vapor / fluent-sqlite-driver Goto Github PK
View Code? Open in Web Editor NEWFluent driver for SQLite
License: MIT License
Fluent driver for SQLite
License: MIT License
The two most popular options for persistence on iOS (Realm and Core Data), both rely heavily on Objective-C, however, SQLite is available. I think there is a good opportunity to not only have a Swift based persistence layer on iOS, but also promote code sharing between Vapor servers and iOS clients.
Concept:
// Shared models package, imported by both client and server
class User: Codable {
let id: Int?
let name: String
let age: Int
}
// Client
extension User: SQLiteModel, Migration {}
// Server
extension User: PostgresModel, Migration {}
I haven't heavily investigated what is keeping users from utilizing FluentSQLite in their iOS apps, but the first thing that comes to mind is the Console package's use of Process()
I wrote a simple User schema with a constraint of a unique email address in the database. In my 'newuser' post method, I am checking the create operation for any database constraint failures. The isConstraintFailure
property is set to false, even though the error is SQLiteNIO.SQLiteError.Reason.constraint
with a message of UNIQUE constraint failed: users.email
.
My function is:
try User.validate(req)
let user = try req.content.decode(User.self)
return user.create(on: req.db)
.map { user }
.flatMapError { (error) -> EventLoopFuture<User> in
if let dbError = error as? DatabaseError, dbError.isConstraintFailure {
return req.eventLoop.makeFailedFuture(Abort(.internalServerError, reason: "User Already exists"))
} else {
return User.query(on: req.db)
.filter(\.$email, .equal, user.email)
.first()
.unwrap(or: Abort(.internalServerError))
}
}
}
On iOS, I have an entity with an optional editingBy: UUID?
field, and once a UUID is assigned to it and saved, then subsequent operations to reset it to nil and save would succeed without exception, but the stored entity still have the previously set value.
To make sure it's the actual cause, I tried set it to a specific UUID before saving if editingBy == nil
, and that specific UUID is saved correctly.
Saved entity would have the field set to nil upon saving.
I'm having a hard time figuring out how to get a pragma value out of SQLite using Fluent. Any pointers?
I've only noticed this behavior when using a SQLite db. This code seems to work fine on PostgreSQL.
This is my initial migration:
struct UserMigration_v1_0_0: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(UserModel.schema)
.id()
.field(UserModel.FieldKeys.email, .string, .required)
.field(UserModel.FieldKeys.password, .string, .required)
.unique(on: UserModel.FieldKeys.email)
.create(),
])
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(UserModel.schema).delete()
])
}
}
(migration 1_1_0 doesn't touch this schema at all)
I then run this migration:
struct UserMigration_v1_2_0: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema(UserModel.schema)
.field(UserModel.FieldKeys.appleId, .string)
.unique(on: UserModel.FieldKeys.appleId)
.update()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(UserModel.schema)
.deleteField(UserModel.FieldKeys.appleId)
.update()
}
}
and the app will crash with the following error:
2020-06-14 20:14:10.556995-0500 xctest[44335:23885144] [logging] near ",": syntax error in "ALTER TABLE "user_users" ADD "appleId" TEXT , ADD CONSTRAINT "uq:user_users.appleId" UNIQUE ("appleId")"
Fatal error: Application.shutdown() was not called before Application deinitialized.: file .../vapor/Sources/Vapor/Application.swift, line 167
Now here's where it gets weird (for me, at least). This crashes on both Mac and Linux, but if I make the following change to the followup migration, it doesn't crash on Mac, but still does on Linux. On Mac, I DO get a console warning (which, when examining the DB file, appears to result in the unique constraint simply just ignored in the end).
struct UserMigration_v1_2_0: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.eventLoop.flatten([
database.schema(UserModel.schema)
.field(UserModel.FieldKeys.appleId, .string)
.update(),
database.schema(UserModel.schema)
.unique(on: UserModel.FieldKeys.appleId)
.update(),
])
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(UserModel.schema)
.deleteField(UserModel.FieldKeys.appleId)
.update()
}
}
Here's the warning I get on macOS:
2020-06-14T20:18:02-0500 warning: database-id=sqlite Ignoring schema update. SQLite only supports adding columns to existing tables
I'm effectively asking for the same end result, but breaking it into two steps allows for the macOS build to simply ignore the issue and continue on.
Another thing I simply don't understand... How can this possible cause the app to deinitialize itself? There are other maintained references to the app that would prevent the reference count from reaching 0, so I feel like I'm going crazy!
Issue: save(on: dbConnection)
for a new instance with a given UUID fails to save to database. Also, no error or feedback is given that the instance was not actually written to the database.
Either of the following would expected:
save(on: dbConnection)
would detect that the non-nil id
was not present in the database and call create(on:)
for an SQL INSERT
.save(on: dbConnection)
would report the update(on:)
SQL UPDATE
failed because the id
was not present in the database.Version: FluentSQLite 3.0.0-rc.2.2
create(on: dbConnection)
worksResult IS FOUND written into the sqlite database.
code
// GET http://localhost:8080/api/zession/crud-class-id-given-create
zessionRoutes.get("crud-class-id-given-create") {
(request: Request) -> Future<ZessionClass> in
// Instantiate Class
let zessionClass = ZessionClass(integer: 11, string: "crud-class-id-given-create")
let uuid11 = UUID.init(uuidString: "dcc7d98d-92f4-46c5-b866-79e67f9c24fe")!
zessionClass.id = uuid11
print("\n@11a uuid11 = \(uuid11.uuidString)")
print("@11b zessionClass = \(zessionClass.description())")
// Create: existing, non-stored, uuid value will be changed.
let didCreate11: EventLoopFuture<ZessionClass> = zessionClass.create(on: request)
print("@11c zessionClass = \(zessionClass.description())")
return didCreate11.map({
(zessionClass: ZessionClass) -> ZessionClass in
print("@11d \(zessionClass.description())")
return zessionClass
})
}
shell
~> curl http://localhost:8080/api/zession/crud-class-id-given-create
{"id":"DCC7D98D-92F4-46C5-B866-79E67F9C24FE","integer":11,"string":"crud-class-id-given-create"}
log
@11a uuid11 = DCC7D98D-92F4-46C5-B866-79E67F9C24FE
@11b zessionClass = id: DCC7D98D-92F4-46C5-B866-79E67F9C24FE integer: 11 string: crud-class-id-given-create
[sqlite] [2018-07-14 05:41:04 +0000] INSERT INTO "ZessionClass" ("id", "integer", "string") VALUES (?, ?, ?)
[0xdcc7d98d92f446c5b86679e67f9c24fe, 11, "crud-class-id-given-create"]
@11c zessionClass = id: DCC7D98D-92F4-46C5-B866-79E67F9C24FE integer: 11 string: crud-class-id-given-create
@11d id: DCC7D98D-92F4-46C5-B866-79E67F9C24FE integer: 11 string: crud-class-id-given-create
save(on: dbConnection)
failsResult IS *NOT* FOUND written into the sqlite database. No error is reported. A JSON HTTP result is erroneously returned as if the row was actually saved in the persistant sqlite database.
code
// GET http://localhost:8080/api/zession/crud-class-id-given-save
zessionRoutes.get("crud-class-id-given-save") {
(request: Request) -> Future<ZessionClass> in
// Instantiate Class
let zessionClass = ZessionClass(integer: 51, string: "crud-class-id-given-save")
let uuid51 = UUID.init(uuidString: "7e0569f4-fafe-40a9-9d8f-e7d4c0e0a273")!
zessionClass.id = uuid51
print("\n@51a uuid51 = \(uuid51.uuidString)")
print("@51b zessionClass = \(zessionClass.description())")
// Create: existing, non-stored, uuid value will be changed.
let didCreate51: EventLoopFuture<ZessionClass> = zessionClass.save(on: request)
print("@51c zessionClass = \(zessionClass.description())")
return didCreate51.map({
(zessionClass: ZessionClass) -> ZessionClass in
print("@51d \(zessionClass.description())")
return zessionClass
})
}
shell
~> curl http://localhost:8080/api/zession/crud-class-id-given-save
{"id":"7E0569F4-FAFE-40A9-9D8F-E7D4C0E0A273","integer":51,"string":"crud-class-id-given-save"}
log
@51a uuid51 = 7E0569F4-FAFE-40A9-9D8F-E7D4C0E0A273
@51b zessionClass = id: 7E0569F4-FAFE-40A9-9D8F-E7D4C0E0A273 integer: 51 string: crud-class-id-given-save
[sqlite] [2018-07-14 05:41:04 +0000] UPDATE "ZessionClass" SET "id" = ?, "integer" = ?, "string" = ? WHERE "ZessionClass"."id" = (?)
[0x7e0569f4fafe40a99d8fe7d4c0e0a273, 51, "crud-class-id-given-save", 0x7e0569f4fafe40a99d8fe7d4c0e0a273]
@51c zessionClass = id: 7E0569F4-FAFE-40A9-9D8F-E7D4C0E0A273 integer: 51 string: crud-class-id-given-save
@51d id: 7E0569F4-FAFE-40A9-9D8F-E7D4C0E0A273 integer: 51 string: crud-class-id-given-save
It would be nice to have a convenient way to support enum in SQLite to be able to represent and maintain model data that have different states/representations.
ex:
enum Make: SQLiteEnum {
case audi, lexus
}
Vapor 3.3.1
FluentSQLite 3.0.0
Using Model:
final class SampleProduct: SQLiteModel {
static var entity: String = "products"
var id: Int?
var title: String
var price: Int
var brandId: Int
var brandName: String
init(id: Int? = nil, title: String, price: Int, brandId: Int, brandName: String) {
self.id = id
self.title = title
self.price = price
self.brandId = brandId
self.brandName = brandName
}
}
trying to group by brandId
column
SampleProduct.query(on: conn).groupBy(\.brandId).all()
getting this error
no such column: SampleProduct.brandId in
"SELECT * FROM "products" GROUP BY "SampleProduct"."brandId""
Fluent is using class name SampleProduct
in GROUP BY
instead of entity name products
While working on another project that's using SQLite, I found another issue relating to the .date
storage type. It can write to the database just fine, however, when reading the data, the server will return an error.
To reproduce, add a date to the default template app:
final class Todo: Model, Content {
static let schema = "todos"
@ID(key: .id)
var id: UUID?
@Field(key: "title")
var title: String
@Field(key: "saveDate")
var date: Date
init() { }
init(id: UUID? = nil, title: String, date: Date) {
self.id = id
self.title = title
self.date = date
}
}
Then in the migration, add the new field with the .date
type:
.field("saveDate", .date, .required)
Finally make a POST
request with the following data:
{
"date": "2020-04-03T00:00:00Z",
"title": "Test"
}
Now when you make a GET
request to retrieve the data, it will return an error message (see below) and failed.
The server should return the saved dates
This error message is sent to me:
[ ERROR ] invalid field: saveDate type: Date error: typeMismatch(Foundation.Date, Swift.DecodingError.Context(codingPath: [], debugDescription: "Could not initialize Date from 1585872000.", underlyingError: nil))
The .datetime
data type works fine without any error.
Say i had a Region
model with one data in it
id | name
_____________
1 | Bellfare region
And say i had a City
model which has region as a foreign key on it
id | name | regionIdFk
1 | BigCity | 2
Now in the City model example above, i put a region foreign id of 2 (which doesnt exist).
Vapor should restrict this and let me know that region with the ID of 2
doesnt exist. But Vapor allows this to work.
Use SQLite Database. Create any foreign key constraint. And do something that should be illegal like above --> Put in a wrong foreign key.
I have ensured that I have done the correct foreign key constraint setup in migration:
final class City: Content {
var id: Int?
let regionId: Region.ID
let name: String
extension City: Migration {
static func prepare(on connection: SQLiteConnection) -> Future<Void> {
return Database.create(self, on: connection) { builder in
try addProperties(to: builder)
try builder.addReference(from: \.regionId, to: \Region.id)
}
}
}
On iOS, creating db raises hang risk warning from Xcode
In my DbCo.swift, I have the following func to setup the db:
static func create(_ config: SQLiteConfiguration, on eventLoop: EventLoop)
-> EventLoopFuture<DbCo>
{
let threadPool = NIOThreadPool(numberOfThreads: 1)
let dbs = Databases(threadPool: threadPool, on: eventLoop)
dbs.use(.sqlite(config), as: .sqlite)
let logger = Logger(label: "sqlite")
let migrations = Migrations()
for v in appMigrations() {
migrations.add(v)
}
let migrator = Migrator(databases: dbs,
migrations: migrations,
logger: logger,
on: eventLoop)
return migrator.setupIfNeeded().flatMap {
migrator.prepareBatch()
}.recover { _ in }.map {
return DbCo(db: dbs.database(logger: logger, on: eventLoop)!,
dbs: dbs)
}.flatMapErrorThrowing { error in
dbs.shutdown()
throw error
}
}
and I call it using:
func setupDb() {
Task {
do {
let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1).next()
dbCo = try await WrinoDbCo.create(on: eventLoop)
} catch {
er(error)
}
}
}
However, it gave me this warning:
/Users/hyouuu/Library/Developer/Xcode/DerivedData/Wrino-ehvehajmnpqfjwathbnvymuedaez/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift:127 Thread running at QOS_CLASS_USER_INITIATED waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions
With backtrace:
DbCo:154: create(on:): dbFilePath:/Users/hyouuu/Library/Developer/CoreSimulator/Devices/4694086C-B7F0-4719-9805-01EC51EC6E42/data/Containers/Data/Application/FEA4668B-A52C-4494-9197-012B7E66E496/Documents/data/db.sqlite
Thread Performance Checker: Thread running at QOS_CLASS_USER_INITIATED waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions
PID: 84797, TID: 2511588
Backtrace
=================================================================
3 Wrino 0x00000001071c5418 $s8NIOPosix27MultiThreadedEventLoopGroupC014setupThreadAnddE033_C2B1528F4FBA68A3DBFA89DBAEBE9D4DLL4name06parentF015selectorFactory11initializerAA010SelectabledE0CSS_AcA8SelectorCyAA15NIORegistrationVGyKcyAA9NIOThreadCctFZ + 1884
4 Wrino 0x00000001071c81c0 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfcAA010SelectabledE0CyAGcXEfU_ + 1852
5 Wrino 0x00000001071d23f4 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfcAA010SelectabledE0CyAGcXEfU_TA + 40
6 libswiftCore.dylib 0x000000018bcc6978 $sSlsE3mapySayqd__Gqd__7ElementQzKXEKlF + 428
7 Wrino 0x00000001071c78d4 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfc + 2132
8 Wrino 0x00000001071c7070 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfC + 72
9 Wrino 0x00000001071c6c9c $s8NIOPosix27MultiThreadedEventLoopGroupC15numberOfThreads15selectorFactoryACSi_AA8SelectorCyAA15NIORegistrationVGyKctcfC + 984
10 Wrino 0x00000001071c6784 $s8NIOPosix27MultiThreadedEventLoopGroupC15numberOfThreadsACSi_tcfC + 404
11 Wrino 0x000000010584a760 $s5Wrino0A2SeC7setupDbyyFyyYaYbcfU_TY0_ + 184
12 libswift_Concurrency.dylib 0x00000001af0754f8 _ZN5swift34runJobInEstablishedExecutorContextEPNS_3JobE + 336
13 libswift_Concurrency.dylib 0x00000001af076170 _ZL17swift_job_runImplPN5swift3JobENS_11ExecutorRefE + 80
14 libdispatch.dylib 0x000000010bf60fb4 _dispatch_continuation_pop + 152
15 libdispatch.dylib 0x000000010bf60190 _dispatch_async_redirect_invoke + 904
16 libdispatch.dylib 0x000000010bf728ac _dispatch_root_queue_drain + 440
17 libdispatch.dylib 0x000000010bf7353c _dispatch_worker_thread2 + 248
18 libsystem_pthread.dylib 0x00000001af2568c0 _pthread_wqthread + 224
19 libsystem_pthread.dylib 0x00000001af2556c0 start_wqthread + 8
Steps to reproduce the behavior:
main
No hang risk
Not sure if my setup code has issue, or caused by any other problems
Saving multiple records within a loop returns objects with duplicate id
s
Records should have unique id
s
Using the 'Todo' class from the Vapor template, add:
func test(_ req: Request) throws -> Future<[Todo]> {
var todos = [Future<Todo>]()
for i in 1...5 {
let todo = Todo(title: "item \(i)")
todos.append(todo.save(on: req).map {
print("todo: \($0.id!), title: \($0.title)")
return $0
})
}
return todos.flatten(on: req)
}
With the in-memory db, the printout yields:
todo: 2, title: item 1
todo: 3, title: item 2
todo: 4, title: item 3
todo: 5, title: item 4
todo: 5, title: item 5
Notice 2 records have the id
5.
With a sqlite db, the console printout is the same, but, peeking in the sqlite.db itself shows that the records do indeed have unique id
s.
"id","title"
1,"item 1"
2,"item 2"
3,"item 3"
4,"item 4"
5,"item 5"
During development I came across this issue.
The model:
final class Game: SQLiteModel {
var id: Int?
}
The code:
Game().create(on: req)
The error:
INSERT INTO "Game" VALUES () []
[logging] near ")": syntax error
This is an enhancement request.
It would be nice to be able to use URL types in my model since URL is codable. They would obviously store as text in the database but would be mapped to a URL when coming from the database.
Fluent 3.0.0
Fluent SQLite 3.0.0
SQL 2.0.2
In implementing the TILApp tutorial, but using SQLite instead of PostgreSQL in chapter 23: Making Categories unique, the following migration fails:
import FluentSQLite
import Vapor
struct MakeCategoriesUnique: Migration {
typealias Database = SQLiteDatabase
static func prepare(on connection: SQLiteConnection) -> Future<Void> {
return Database.update(Category.self, on: connection) { builder in
builder.unique(on: \.name)
}
}
static func revert(on connection: SQLiteConnection) -> Future<Void> {
return Database.update(Category.self, on: connection) { builder in
builder.deleteUnique(from: \.name)
}
}
}
with fatalError("SQLite only supports adding one (1) column in an ALTER query.")
in FluentSQLite/SQLiteDatabase+SchemaSupporting.swift:75
:
/// See `SchemaSupporting`.
public static func schemaExecute(_ fluent: Schema, on conn: SQLiteConnection) -> Future<Void> {
let query: SQLiteQuery
switch fluent.statement {
case ._createTable:
var createTable: SQLiteCreateTable = .createTable(fluent.table)
createTable.columns = fluent.columns
createTable.tableConstraints = fluent.constraints
query = ._createTable(createTable)
case ._alterTable:
guard fluent.columns.count == 1 && fluent.constraints.count == 0 else {
jjprint(fluent.columns.count) // 0 columns
jjprint(fluent.constraints.count) // 1 constraint
/// See https://www.sqlite.org/lang_altertable.html
/*>>>>>>>>>*/ fatalError("SQLite only supports adding one (1) column in an ALTER query.")
}
query = .alterTable(.init(
table: fluent.table,
value: .addColumn(fluent.columns[0])
))
case ._dropTable:
let dropTable: SQLiteDropTable = .dropTable(fluent.table)
query = ._dropTable(dropTable)
}
return conn.query(query).transform(to: ())
}
Notice that fluent.columns.count
is 0 and fluent.constraints.count
is 1. Which is the contrary of what the guard condition expects.
Apparently, you can't add constraints to SQLite columns without recreating the table:
But you can add a unique index to the table:
CREATE UNIQUE INDEX `uq:Category.name` ON Category(name);
Is that a behavior that FluentSQLite could adopt? Or is the cause of this fatalError somewhere else?
I am attempting to write a migration to delete a column. However, in testing, I receive previousError(SQLite only supports adding columns in ALTER TABLE statements.)
when I attempt to run the migration, and the database is not updated.
In reduced form:
struct Migration1: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("my_table")
.field("field", .double, .required)
.field("other_field", .double)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("my_table").delete()
}
}
struct Migration2: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
database.schema("my_table")
.deleteField("field")
.update()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema("my_table")
.field("field", .double, .required)
.update()
}
}
Steps to reproduce the behavior:
The end result for the table "my_table" should be a schema of a single column named "other_field".
Grabbing relevant sections from my Package.resolved:
{
"package": "fluent",
"repositoryURL": "https://github.com/vapor/fluent.git",
"state": {
"branch": null,
"revision": "855cd81cd129675dcb9adadeca2286281dbc0190",
"version": "4.1.0"
}
},
{
"package": "fluent-kit",
"repositoryURL": "https://github.com/vapor/fluent-kit.git",
"state": {
"branch": null,
"revision": "86ebecdb98981b7a242746209b4335770bf0bd11",
"version": "1.10.2"
}
},
{
"package": "fluent-sqlite-driver",
"repositoryURL": "https://github.com/vapor/fluent-sqlite-driver.git",
"state": {
"branch": null,
"revision": "6f29f6f182c812075f09c7575c18ac5535c26824",
"version": "4.0.1"
}
},
{
"package": "vapor",
"repositoryURL": "https://github.com/vapor/vapor.git",
"state": {
"branch": null,
"revision": "5148f02e42149f73f6616452062cf96b64afbda6",
"version": "4.36.2"
}
}
vapor --version
yields Error: Unknown command '--version'
no such table error when creating a pivot object by attach
final class Branch: Model, Content {
static let schema = "t_branch"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Siblings(through: BranchPerson.self, from: \.$branch, to: \.$person)
var persons: [Person]
init() { }
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
final class Person: Model, Content {
static let schema = "t_person"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Siblings(through: BranchPerson.self, from: \.$person, to: \.$branch)
var branches: [Branch]
init() { }
init(id: UUID? = nil, name: String) {
self.id = id
self.name = name
}
}
final class BranchPerson: Model, Content {
static let schema = "t_branch_person"
@ID(key: .id)
var id: UUID?
@Parent(key: "branchId")
var branch: Branch
@Parent(key: "personId")
var person: Person
init() { }
init(id: UUID? = nil, branch: Branch, person: Person) throws {
self.id = id
self.$branch.id = try branch.requireID()
self.$person.id = try person.requireID()
}
}
struct CreateBranchPerson: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("t_branch_person")
.id()
.field("branchId", .uuid, .required, .references("branch", "id", onDelete: .cascade))
.field("personId", .uuid, .required, .references("person", "id", onDelete: .cascade))
.unique(on: "branchId", "personId") // 不允许重复的关系
.create()
}
func revert(on database: Database) async throws {
try await database.schema("t_branch_person").delete()
}
}
public func configure(_ app: Application) throws {
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.views.use(.leaf)
app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
app.migrations.add(CreateBranch())
app.migrations.add(CreatePerson())
app.migrations.add(CreateBranchPerson())
try routes(app)
}
struct PersonWatchOnBranchController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.post("person", ":personId", "watchOn", ":branchId", use: createPersonWatchOnBranchHandler)
}
func createPersonWatchOnBranchHandler(req: Request) async throws -> HTTPStatus {
guard let person = try await Person.find(req.parameters.get("personId"), on: req.db),
let branch = try await Branch.find(req.parameters.get("branchId"), on: req.db)
else {
throw Abort(.notFound)
}
// 只有当关系不存在时才附加
try await person.$branches.attach(branch, method: .ifNotExists, on: req.db)
//try await branch.$persons.attach(person, method: .ifNotExists, on: req.db)
return .ok
}
}
Steps to reproduce the behavior:
let package = Package(
name: "ipas",
platforms: [
.macOS(.v12)
],
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/vapor.git", from: "4.67.1"),
.package(url: "https://github.com/vapor/fluent.git", from: "4.5.0"),
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.2.0"),
.package(url: "https://github.com/m-barthelemy/vapor-queues-fluent-driver.git", from: "3.0.0-beta1"),
.package(url: "https://github.com/vapor/leaf.git", from: "4.2.2"),
],
targets: [
.target(
name: "App",
dependencies: [
.product(name: "Fluent", package: "fluent"),
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
.product(name: "QueuesFluentDriver", package: "vapor-queues-fluent-driver"),
.product(name: "Leaf", package: "leaf"),
.product(name: "Vapor", package: "vapor")
],
swiftSettings: [
// Enable better optimizations when building in Release configuration. Despite the use of
// the `.unsafeFlags` construct required by SwiftPM, this flag is recommended for Release
// builds. See <https://github.com/swift-server/guides/blob/main/docs/building.md#building-for-production> for details.
.unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
]
),
.executableTarget(name: "Run", dependencies: [.target(name: "App")]),
.testTarget(name: "AppTests", dependencies: [
.target(name: "App"),
.product(name: "XCTVapor", package: "vapor"),
])
]
)
[ INFO ] POST /person/D9B06675-532F-45A8-BD93-CA10241DDA7B/watchOn/8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7 [request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (Vapor/RouteLoggingMiddleware.swift:13)
[ DEBUG ] query read t_person filters=[t_person[id] = D9B06675-532F-45A8-BD93-CA10241DDA7B] limits=[count(1)] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (FluentKit/QueryBuilder.swift:293)
[ DEBUG ] No available connections on this event loop, creating a new one (AsyncKit/EventLoopConnectionPool.swift:202)
[ DEBUG ] Connected to sqlite db: db.sqlite (SQLiteNIO/SQLiteConnection.swift:112)
[ DEBUG ] PRAGMA foreign_keys = ON [] (SQLiteNIO/SQLiteConnection.swift:161)
[ DEBUG ] SELECT "t_person"."id" AS "t_person_id", "t_person"."name" AS "t_person_name", "t_person"."email" AS "t_person_email", "t_person"."telephone" AS "t_person_telephone", "t_person"."dingTalkId" AS "t_person_dingTalkId" FROM "t_person" WHERE "t_person"."id" = ? LIMIT 1 ["D9B06675-532F-45A8-BD93-CA10241DDA7B"] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (SQLiteNIO/SQLiteConnection.swift:161)
[ DEBUG ] query read t_branch filters=[t_branch[id] = 8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7] limits=[count(1)] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (FluentKit/QueryBuilder.swift:293)
[ DEBUG ] SELECT "t_branch"."id" AS "t_branch_id", "t_branch"."name" AS "t_branch_name" FROM "t_branch" WHERE "t_branch"."id" = ? LIMIT 1 ["8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7"] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (SQLiteNIO/SQLiteConnection.swift:161)
[ DEBUG ] query read t_branch_person filters=[t_branch_person[personId] = D9B06675-532F-45A8-BD93-CA10241DDA7B, t_branch_person[branchId] = 8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7] limits=[count(1)] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (FluentKit/QueryBuilder.swift:293)
[ DEBUG ] SELECT "t_branch_person"."id" AS "t_branch_person_id", "t_branch_person"."branchId" AS "t_branch_person_branchId", "t_branch_person"."personId" AS "t_branch_person_personId" FROM "t_branch_person" WHERE "t_branch_person"."personId" = ? AND "t_branch_person"."branchId" = ? LIMIT 1 ["D9B06675-532F-45A8-BD93-CA10241DDA7B", "8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7"] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (SQLiteNIO/SQLiteConnection.swift:161)
[ DEBUG ] query create t_branch_person input=[[branchId: 8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7, personId: D9B06675-532F-45A8-BD93-CA10241DDA7B, id: 265A29D6-8205-48B1-B225-E838BCDA3E63]] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (FluentKit/QueryBuilder.swift:293)
[ DEBUG ] INSERT INTO "t_branch_person" ("branchId", "personId", "id") VALUES (?, ?, ?) ["8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7", "D9B06675-532F-45A8-BD93-CA10241DDA7B", "265A29D6-8205-48B1-B225-E838BCDA3E63"] [database-id: sqlite, request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (SQLiteNIO/SQLiteConnection.swift:161)
[ WARNING ] error: no such table: main.person [request-id: EF168AC8-6511-4E7B-9F59-11C40A1176DA] (Vapor/Middleware/ErrorMiddleware.swift:42)
create a pivot (BranchPerson) successfully without any error.
{
"identity" : "vapor",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/vapor.git",
"state" : {
"revision" : "0eacdf3c7eab6df493ce328f2e97c2873b3e1c1e",
"version" : "4.67.1"
}
}
I have a model with its ID stored as a String representing an hex value. When I read an item where the ID string is just numbers I get [ ERROR ] invalid field: id type: Optional<String> error: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Could not initialize String from 12345.", underlyingError: nil))
To reproduce add a model with a String as ID
final class Device: Model, Content {
typealias IDValue = String
// Name of the table or collection.
static let schema = "devices"
// Unique identifier for this Device.
@ID(custom: "id")
var id: String?
// The device's name.
@Field(key: "name")
var name: String?
// Creates a new, empty Device.
init() { }
// Creates a new Device with all properties set.
init(id: String, name: String) {
self.id = id
self.name = name
}
}
Then save a device using
let newDevice = Device(id: "12345", name: "myDevice")
newDevice.create(on: req.db).whenSuccess({})
Finally query that ID
let deviceOnDB: EventLoopFuture<Device?> = Device.find("12345", on: req.db)
deviceOnDB.whenComplete { (result) in
switch result {
case .success(let device):
if let device = device {
req.logger.info("Success")
}
case .failure(let error):
req.logger.error("Failure")
}
}
The query returns the saved device
This error message is sent to me:
[ ERROR ] invalid field: id type: Optional<String> error: typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Could not initialize String from 12345.", underlyingError: nil))
If I query the database myself I can get the saved objet just fine
*Framework: 4.30.0
*Toolbox: 18.3.0
*OS version: macOS 11.1
If I save an ID containing a letter like "12345A" it does work.
Moving vapor/fluent#373 here
Moving vapor/fluent#365 here
In Fluent 3, it looks like this should be done when defining a custom migration for a model, using the addProperties(to:)
function. However, this involves setting an array of SchemaField
s, which do not have a isUnique
function on them, or a way I can tell to specify that.
FLite is aim to be used with iOS to save data using SQLite.
Vapor 3 is supported and working in the master
branch.
Vapor 4 has the iOS test passing, but fails on an actual iOS project.
FLite 0.2.0 is using Vapor 4
I am getting this error on an iOS project
Fatal error: Unexpected error while running SelectableEventLoop: kqueue(): Too many open files (errno: 24).: file /Users/cri/Library/Developer/Xcode/DerivedData/test-gsikgwqyfpktaldxljjiybotjrwo/SourcePackages/checkouts/swift-nio/Sources/NIO/EventLoop.swift, line 828
2020-07-08 16:59:03.742934-0500 test[94438:1326247] Fatal error: Unexpected error while running SelectableEventLoop: kqueue(): Too many open files (errno: 24).: file /Users/cri/Library/Developer/Xcode/DerivedData/test-gsikgwqyfpktaldxljjiybotjrwo/SourcePackages/checkouts/swift-nio/Sources/NIO/EventLoop.swift, line 828
Here is the example iOS project code
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let semaphore = DispatchSemaphore(value: 0)
var values = [Todo]()
try? FLite.prepare(migration: Todo.self).wait()
try! FLite.add(model: Todo(title: "Hello World", strings: ["hello", "world"])).wait()
FLite.fetch(model: Todo.self)
.whenSuccess { (todos) in
values = todos
semaphore.signal()
}
semaphore.wait()
print("Values: \(values)")
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.