Code Monkey home page Code Monkey logo

fluent-sqlite-driver's Introduction

FluentSQLite

Documentation Team Chat MIT License Continuous Integration Swift 5.7+


fluent-sqlite-driver's People

Contributors

0xtim avatar bennydebock avatar calebkleveter avatar gwynne avatar hyouuu avatar jaapwijnen avatar madsodgaard avatar mihaelisaev avatar tanner0101 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluent-sqlite-driver's Issues

Make FluentSQLite usable on iOS

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()

DatabaseError.isConstraintFailure not being set

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))
                }
        }
    }

nil field does not save

Describe the bug

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.

Expected behavior

Saved entity would have the field set to nil upon saving.

Environment

  • Vapor Framework version: NA - on latest iOS
  • OS version: iOS 17.2.1, macOS 14.2.1
Screenshot 2024-01-05 at 12 01 50 AM

Getting a pragma value

I'm having a hard time figuring out how to get a pragma value out of SQLite using Fluent. Any pointers?

Adding Unique constraint can cause vapor to crash

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!

'save(on: connection)' with given UUID fails to save to database

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:

  1. [preferred] save(on: dbConnection) would detect that the non-nil id was not present in the database and call create(on:) for an SQL INSERT.
  2. [not preferred] 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

Example create(on: dbConnection) works

Result 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 

Example save(on: dbConnection) fails

Result 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 

Add Support for Enum type

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
}

GROUP BY uses model's class name instead of entity name

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

FluentSQLiteDriver cannot read value with type .date

Steps to reproduce

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.

Expected behavior

The server should return the saved dates

Actual behavior

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.

Environment

  • Vapor Framework version: 4.4.0
  • FluentSQLiteDriver version: 4.0.0-rc1.1
  • OS version: macOS 10.15.4

Vapor does not honor foreign key constraints when using SQLite

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.

Steps to reproduce

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)
        }
    }
}
  • Vapor Framework version: 3.0.0-rc.2.2.4
  • Vapor Toolbox version: Vapor Toolbox: 3.1.4
  • OS version: High Sierra

On iOS, creating db raises hang risk warning from Xcode

Describe the bug

On iOS, creating db raises hang risk warning from Xcode

To Reproduce

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:

  1. Add package with configuration 'https://github.com/vapor/fluent-sqlite-driver' from main
  2. Call setupDb
  3. See error

Expected behavior

No hang risk

Environment

  • Vapor Framework version: N/A
  • Vapor Toolbox version: N/A
  • OS version: macOS running Ventura 13.0.1
  • Xcode: 14.1
  • iOS: 16.1

Additional context

Not sure if my setup code has issue, or caused by any other problems

Saving multiple records results in duplicate 'id's

Bug

Saving multiple records within a loop returns objects with duplicate ids

Expected Result

Records should have unique ids

Steps to reproduce

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 ids.

"id","title"
1,"item 1"
2,"item 2"
3,"item 3"
4,"item 4"
5,"item 5"

Can't create model with only id

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

Add support for URL

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.

Migration adding unique constraint fails.

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:

https://stackoverflow.com/questions/15497985/how-to-add-unique-constraint-in-already-made-table-in-sqlite-ios

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?

Unable to delete columns using Schema Builder/Migration

Describe the bug

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.

To Reproduce

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:

  1. Define the two migrations above and attempt to run them in order.

Expected behavior

The end result for the table "my_table" should be a schema of a single column named "other_field".

Environment

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 Framework version: 4.36.2
  • Vapor Toolbox version: Unknown; vapor --version yields Error: Unknown command '--version'
  • OS version: macOS 11.2.3; Xcode 12.4 (12D4e)

no such table error when creating a pivot object by attach

Describe the bug

no such table error when creating a pivot object by attach

To Reproduce

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:

  1. Add package with configuration '...'
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"),
        ])
    ]
)
  1. Send request with options '...'

POST http://127.0.0.1:8080/person/D9B06675-532F-45A8-BD93-CA10241DDA7B/watchOn/8F25A197-1BC8-4679-BC28-E4AC3F1CDAF7

  1. See error
[ 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)

Expected behavior

create a pivot (BranchPerson) successfully without any error.

Environment

  • Vapor Framework version:
{
      "identity" : "vapor",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/vapor.git",
      "state" : {
        "revision" : "0eacdf3c7eab6df493ce328f2e97c2873b3e1c1e",
        "version" : "4.67.1"
      }
    }
  • Vapor Toolbox version: toolbox: 18.6.0
  • OS version: 12.6 (21G115)

typeMismatch when reading a number stored as string

Describe the bug

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

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")
    }
}

Expected behavior

The query returns the saved device

Actual behavior

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

Environment

*Framework: 4.30.0
*Toolbox: 18.3.0
*OS version: macOS 11.1

Additional context

If I save an ID containing a letter like "12345A" it does work.

find function is throwing error

screen shot 2018-05-24 at 1 33 37 pm

I get the following error:
error: Cannot convert value of type '() throws -> EventLoopFuture<Dish?>' to expected argument type '(Request) throws -> '

Allow specification of uniqueness of a field

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 SchemaFields, which do not have a isUnique function on them, or a way I can tell to specify that.

Example Fluent-SQLite iOS

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)")
    }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.