From a40fd2baeab7d5b42e9ed7aa8c366ac37e853e90 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 5 Sep 2025 16:14:02 -0700 Subject: [PATCH 1/5] CloudKit: Use virtual table helpers Let's prefer our builder over SQL strings when it's easy to do so. --- Package.resolved | 6 +- Package.swift | 3 +- Package@swift-6.0.swift | 2 +- .../CloudKit/Internal/ForeignKey.swift | 57 +--------- .../CloudKit/Internal/Pragmas.swift | 64 +++++++++++ .../CloudKit/Internal/TableInfo.swift | 38 +------ Sources/SQLiteData/CloudKit/SyncEngine.swift | 107 ++++++++++-------- .../CloudKitTests/CloudKitTests.swift | 62 +++++----- .../CloudKitTests/RecordTypeTests.swift | 76 ++++++------- .../CloudKitTests/SharingTests.swift | 4 +- .../SyncEngineValidationTests.swift | 6 +- 11 files changed, 212 insertions(+), 213 deletions(-) create mode 100644 Sources/SQLiteData/CloudKit/Internal/Pragmas.swift diff --git a/Package.resolved b/Package.resolved index d2942708..95164992 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "3b49a4e324dfd736adfe38cb30f7c3a771fb77d8faee549e703df3e8b4f7f8fd", + "originHash" : "da6d37ac47387e727b920502ddb8de7c46a58352f302cdeb26f3a1df3c3307bb", "pins" : [ { "identity" : "combine-schedulers", @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "adad5c6c5abe0c62f93c573de5be071043f621a8", - "version" : "0.17.0" + "branch" : "vtable-join", + "revision" : "c974d4e541a14f37745c1ddfeae664aba4c614b3" } }, { diff --git a/Package.swift b/Package.swift index 9c740c67..705ad8ba 100644 --- a/Package.swift +++ b/Package.swift @@ -36,7 +36,8 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"), .package( url: "https://github.com/pointfreeco/swift-structured-queries", - from: "0.17.0", +// from: "0.17.0", + branch: "vtable-join", traits: [ .trait(name: "StructuredQueriesTagged", condition: .when(traits: ["SQLiteDataTagged"])) ] diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index b1241a7f..d013bca9 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.9.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.3.0"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"), - .package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.17.0"), + .package(url: "https://github.com/pointfreeco/swift-structured-queries", branch: "vtable-join"), // from: "0.17.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.5.0"), ], targets: [ diff --git a/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift b/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift index 4dd5461a..7ad65872 100644 --- a/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift +++ b/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift @@ -2,60 +2,13 @@ import Foundation import StructuredQueriesCore - struct ForeignKey: QueryDecodable, QueryRepresentable { - typealias QueryValue = Self - + @Selection + struct ForeignKey { let table: String let from: String let to: String - let onUpdate: Action - let onDelete: Action - let notnull: Bool - - init(decoder: inout some QueryDecoder) throws { - guard - let table = try decoder.decode(String.self), - let from = try decoder.decode(String.self), - let to = try decoder.decode(String.self), - let onUpdate = try decoder.decode(Action.self), - let onDelete = try decoder.decode(Action.self), - let notnull = try decoder.decode(Bool.self) - else { - throw QueryDecodingError.missingRequiredColumn - } - self.table = table - self.from = from - self.to = to - self.onUpdate = onUpdate - self.onDelete = onDelete - self.notnull = notnull - } - - enum Action: String, QueryBindable { - case cascade = "CASCADE" - case restrict = "RESTRICT" - case setDefault = "SET DEFAULT" - case setNull = "SET NULL" - case noAction = "NO ACTION" - } - - static func all( - _ tableName: String - ) -> some StructuredQueriesCore.Statement { - #sql( - """ - SELECT \(columns) - FROM pragma_foreign_key_list(\(bind: tableName)) AS "foreign_keys" - JOIN pragma_table_info(\(bind: tableName)) AS "table_info" - ON "foreign_keys"."from" = "table_info"."name" - """ - ) - } - - static var columns: QueryFragment { - """ - "table", "from", "to", "on_update", "on_delete", "notnull" - """ - } + let onUpdate: ForeignKeyAction + let onDelete: ForeignKeyAction + let isNotNull: Bool } #endif diff --git a/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift b/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift new file mode 100644 index 00000000..50d1b385 --- /dev/null +++ b/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift @@ -0,0 +1,64 @@ +@Table +struct PragmaDatabaseList { + static var tableAlias: String? { "databases" } + static var tableFragment: QueryFragment { "pragma_database_list()" } + + @Column("seq") let sequence: Int + let name: String + let file: String +} + +@Table +struct PragmaForeignKeyList { + static var tableAlias: String? { "\(Base.tableName)ForeignKeys" } + static var tableFragment: QueryFragment { + "pragma_foreign_key_list(\(quote: Base.tableName, delimiter: .text))" + } + + let id: Int + @Column("seq") let sequence: Int + let table: String + let from: String + let to: String + @Column("on_update") let onUpdate: ForeignKeyAction + @Column("on_delete") let onDelete: ForeignKeyAction + let match: String +} + +enum ForeignKeyAction: String, QueryBindable { + case cascade = "CASCADE" + case restrict = "RESTRICT" + case setDefault = "SET DEFAULT" + case setNull = "SET NULL" + case noAction = "NO ACTION" +} + +@Table +struct PragmaIndexList { + static var tableAlias: String? { "\(Base.tableName)Indices" } + static var tableFragment: QueryFragment { + "pragma_index_list(\(quote: Base.tableName, delimiter: .text))" + } + + @Column("seq") let sequence: Int + let name: String + @Column("unique") let isUnique: Bool + let origin: String + @Column("partial") let isPartial: Bool +} + +@Table +struct PragmaTableInfo { + static var tableAlias: String? { "\(Base.tableName)TableInfo" } + static var schemaName: String? { Base.schemaName } + static var tableFragment: QueryFragment { + "pragma_table_info(\(quote: Base.tableName, delimiter: .text))" + } + + @Column("cid") let columnID: Int + let name: String + let type: String + @Column("notnull") let isNotNull: Bool + @Column("dflt_value") let defaultValue: String? + @Column("pk") let isPrimaryKey: Bool +} diff --git a/Sources/SQLiteData/CloudKit/Internal/TableInfo.swift b/Sources/SQLiteData/CloudKit/Internal/TableInfo.swift index 462615b8..1b416666 100644 --- a/Sources/SQLiteData/CloudKit/Internal/TableInfo.swift +++ b/Sources/SQLiteData/CloudKit/Internal/TableInfo.swift @@ -1,44 +1,10 @@ import StructuredQueriesCore +@Selection package struct TableInfo: Codable, Hashable, QueryDecodable, QueryRepresentable { - typealias QueryValue = Self - let defaultValue: String? let isPrimaryKey: Bool let name: String - let notNull: Bool + let isNotNull: Bool let type: String - - package init(decoder: inout some QueryDecoder) throws { - self.defaultValue = try decoder.decode(String.self) - guard - let isPrimaryKey = try decoder.decode(Bool.self), - let name = try decoder.decode(String.self), - let notNull = try decoder.decode(Bool.self), - let type = try decoder.decode(String.self) - else { - throw QueryDecodingError.missingRequiredColumn - } - self.isPrimaryKey = isPrimaryKey - self.name = name - self.notNull = notNull - self.type = type - } - - static func all( - _ tableName: String - ) -> some StructuredQueriesCore.Statement { - #sql( - """ - SELECT \(columns) FROM pragma_table_info(\(bind: tableName)) - """, - as: Self.self - ) - } - - static var columns: QueryFragment { - """ - "dflt_value", "pk", "name", "notnull", "type" - """ - } } diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 78c8e150..8b72246b 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -207,10 +207,25 @@ let foreignKeysByTableName = Dictionary( uniqueKeysWithValues: try userDatabase.read { db in try allTables.map { table -> (String, [ForeignKey]) in - ( - table.tableName, - try ForeignKey.all(table.tableName).fetchAll(db) - ) + func open(_: T.Type) throws -> (String, [ForeignKey]) { + ( + table.tableName, + try PragmaForeignKeyList + .join(PragmaTableInfo.all) { $0.from.eq($1.name) } + .select { + ForeignKey.Columns( + table: $0.table, + from: $0.from, + to: $0.to, + onUpdate: $0.onUpdate, + onDelete: $0.onDelete, + isNotNull: $1.isNotNull + ) + } + .fetchAll(db) + ) + } + return try open(table) } } ) @@ -250,14 +265,9 @@ try userDatabase.write { db in let attachedMetadatabasePath: String? = - try #sql( - """ - SELECT "file" - FROM pragma_database_list() - WHERE "name" = \(bind: String.sqliteDataCloudKitSchemaName) - """, - as: String.self - ) + try PragmaDatabaseList + .where { $0.name.eq(String.sqliteDataCloudKitSchemaName) } + .select(\.file) .fetchOne(db) if let attachedMetadatabasePath { let attachedMetadatabaseName = URL(filePath: metadatabase.path).lastPathComponent @@ -359,13 +369,28 @@ } .fetchAll(db) return try namesAndSchemas.compactMap { schema -> RecordType? in - guard let sql = schema.sql + guard let sql = schema.sql, let table = tablesByName[schema.name] else { return nil } - return RecordType( - tableName: schema.name, - schema: sql, - tableInfo: Set(try TableInfo.all(schema.name).fetchAll(db)) - ) + func open(_: T.Type) throws -> RecordType { + try RecordType( + tableName: schema.name, + schema: sql, + tableInfo: Set( + PragmaTableInfo + .select { + TableInfo.Columns( + defaultValue: $0.defaultValue, + isPrimaryKey: $0.isPrimaryKey, + name: $0.name, + isNotNull: $0.isNotNull, + type: $0.type + ) + } + .fetchAll(db) + ) + ) + } + return try open(table) } } let previousRecordTypeByTableName = Dictionary( @@ -1749,13 +1774,7 @@ ) } - let databasePath = try #sql( - """ - SELECT "file" FROM pragma_database_list() - """, - as: String.self - ) - .fetchOne(self) + let databasePath = try PragmaDatabaseList.select(\.file).fetchOne(self) guard let databasePath else { struct PathError: Error {} throw SyncEngine.SchemaError( @@ -1848,23 +1867,21 @@ } for table in tables { - let columnsWithUniqueConstraints = - try #sql( - """ - SELECT "name" FROM pragma_index_list(\(quote: table.tableName, delimiter: .text)) - WHERE "unique" = 1 AND "origin" <> 'pk' - """, - as: String.self - ) - .fetchAll(db) - if !columnsWithUniqueConstraints.isEmpty { - throw SyncEngine.SchemaError( - reason: .uniquenessConstraint, - debugDescription: """ + func open(_: T.Type) throws { + let columnsWithUniqueConstraints = try PragmaIndexList + .where { $0.isUnique && $0.origin != "pk" } + .select(\.name) + .fetchAll(db) + if !columnsWithUniqueConstraints.isEmpty { + throw SyncEngine.SchemaError( + reason: .uniquenessConstraint, + debugDescription: """ Uniqueness constraints are not supported for synchronized tables. """ - ) + ) + } } + try open(table) } } } @@ -1892,13 +1909,11 @@ let tableDependencies = try userDatabase.read { db in var dependencies: [HashablePrimaryKeyedTableType: [any PrimaryKeyedTable.Type]] = [:] for table in tables { - let toTables = try #sql( - """ - SELECT "table" FROM pragma_foreign_key_list(\(quote: table.tableName, delimiter: .text)) - """, - as: String.self - ) - .fetchAll(db) + func open(_: T.Type) throws -> [String] { + try PragmaForeignKeyList.select(\.table) + .fetchAll(db) + } + let toTables = try open(table) for toTable in toTables { guard let toTableType = tablesByName[toTable] else { continue } diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 89d9a155..9daa6d3d 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -32,14 +32,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -58,21 +58,21 @@ defaultValue: nil, isPrimaryKey: false, name: "coverImage", - notNull: true, + isNotNull: true, type: "BLOB" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -91,21 +91,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "position", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -129,42 +129,42 @@ defaultValue: nil, isPrimaryKey: false, name: "dueDate", - notNull: false, + isNotNull: false, type: "TEXT" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "isCompleted", - notNull: true, + isNotNull: true, type: "INTEGER" ), [3]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "priority", - notNull: false, + isNotNull: false, type: "INTEGER" ), [4]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [5]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -181,7 +181,7 @@ defaultValue: nil, isPrimaryKey: true, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -200,21 +200,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "reminderID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "tagID", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -231,7 +231,7 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -249,14 +249,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "parentID", - notNull: false, + isNotNull: false, type: "INTEGER" ) ] @@ -275,14 +275,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "parentID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -301,14 +301,14 @@ defaultValue: "0", isPrimaryKey: false, name: "count", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -327,21 +327,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "isOn", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "modelAID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -360,21 +360,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "modelBID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] diff --git a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift index 69a2dbf9..f5b13b02 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift @@ -31,14 +31,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -57,21 +57,21 @@ defaultValue: nil, isPrimaryKey: false, name: "coverImage", - notNull: true, + isNotNull: true, type: "BLOB" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -90,21 +90,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "position", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -128,42 +128,42 @@ defaultValue: nil, isPrimaryKey: false, name: "dueDate", - notNull: false, + isNotNull: false, type: "TEXT" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "isCompleted", - notNull: true, + isNotNull: true, type: "INTEGER" ), [3]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "priority", - notNull: false, + isNotNull: false, type: "INTEGER" ), [4]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [5]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -180,7 +180,7 @@ defaultValue: nil, isPrimaryKey: true, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -199,21 +199,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "reminderID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "tagID", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -230,7 +230,7 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -248,14 +248,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "parentID", - notNull: false, + isNotNull: false, type: "INTEGER" ) ] @@ -274,14 +274,14 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "parentID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -300,14 +300,14 @@ defaultValue: "0", isPrimaryKey: false, name: "count", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -326,21 +326,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "isOn", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "modelAID", - notNull: true, + isNotNull: true, type: "INTEGER" ) ] @@ -359,21 +359,21 @@ defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "modelBID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] @@ -459,49 +459,49 @@ defaultValue: nil, isPrimaryKey: false, name: "dueDate", - notNull: false, + isNotNull: false, type: "TEXT" ), [1]: TableInfo( defaultValue: nil, isPrimaryKey: true, name: "id", - notNull: true, + isNotNull: true, type: "INTEGER" ), [2]: TableInfo( defaultValue: "0", isPrimaryKey: false, name: "isCompleted", - notNull: true, + isNotNull: true, type: "INTEGER" ), [3]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "newFeature", - notNull: true, + isNotNull: true, type: "INTEGER" ), [4]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "priority", - notNull: false, + isNotNull: false, type: "INTEGER" ), [5]: TableInfo( defaultValue: nil, isPrimaryKey: false, name: "remindersListID", - notNull: true, + isNotNull: true, type: "INTEGER" ), [6]: TableInfo( defaultValue: "\'\'", isPrimaryKey: false, name: "title", - notNull: true, + isNotNull: true, type: "TEXT" ) ] diff --git a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift index a43fe6da..b6bbfc81 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SharingTests.swift @@ -45,7 +45,7 @@ to: "id", onUpdate: .cascade, onDelete: .cascade, - notnull: true + isNotNull: true ) ] ), @@ -112,7 +112,7 @@ to: "id", onUpdate: .noAction, onDelete: .cascade, - notnull: true + isNotNull: true ) ] ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineValidationTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineValidationTests.swift index 6f3b5f62..890730de 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SyncEngineValidationTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SyncEngineValidationTests.swift @@ -96,7 +96,7 @@ to: "id", onUpdate: .noAction, onDelete: .noAction, - notnull: false + isNotNull: false ) ), debugDescription: #"Foreign key "childs"."parentID" action not supported. Must be 'CASCADE', 'SET DEFAULT' or 'SET NULL'."# @@ -155,7 +155,7 @@ to: "id", onUpdate: .noAction, onDelete: .restrict, - notnull: false + isNotNull: false ) ), debugDescription: #"Foreign key "childs"."parentID" action not supported. Must be 'CASCADE', 'SET DEFAULT' or 'SET NULL'."# @@ -221,7 +221,7 @@ to: "id", onUpdate: .noAction, onDelete: .cascade, - notnull: false + isNotNull: false ) ), debugDescription: #"Foreign key "childs"."parentID" references table "parents" that is not synchronized. Update 'SyncEngine.init' to synchronize "parents". "# From 82b40c12ae4d8f8f0f9424fb33fbb31cbaf7e267 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 5 Sep 2025 16:49:50 -0700 Subject: [PATCH 2/5] wip --- Package.resolved | 6 +++--- Package.swift | 3 +-- Package@swift-6.0.swift | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Package.resolved b/Package.resolved index 95164992..47cd361e 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "da6d37ac47387e727b920502ddb8de7c46a58352f302cdeb26f3a1df3c3307bb", + "originHash" : "3a7b88b10aac321547bc7235de2c7480456aca2003491837d36ba7dea7636a30", "pins" : [ { "identity" : "combine-schedulers", @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "branch" : "vtable-join", - "revision" : "c974d4e541a14f37745c1ddfeae664aba4c614b3" + "revision" : "49f18c24145a6e061cc581662f468b7c18523b8d", + "version" : "0.18.0" } }, { diff --git a/Package.swift b/Package.swift index 705ad8ba..ad14a969 100644 --- a/Package.swift +++ b/Package.swift @@ -36,8 +36,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"), .package( url: "https://github.com/pointfreeco/swift-structured-queries", -// from: "0.17.0", - branch: "vtable-join", + from: "0.18.0", traits: [ .trait(name: "StructuredQueriesTagged", condition: .when(traits: ["SQLiteDataTagged"])) ] diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index d013bca9..f69570ea 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -27,7 +27,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.9.0"), .package(url: "https://github.com/pointfreeco/swift-sharing", from: "2.3.0"), .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.18.4"), - .package(url: "https://github.com/pointfreeco/swift-structured-queries", branch: "vtable-join"), // from: "0.17.0"), + .package(url: "https://github.com/pointfreeco/swift-structured-queries", from: "0.18.0"), .package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.5.0"), ], targets: [ From 177957b1f33fcc8bdd65a899b3088a54d21cea37 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 5 Sep 2025 17:03:38 -0700 Subject: [PATCH 3/5] wip --- Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift b/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift index 7ad65872..977dc785 100644 --- a/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift +++ b/Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift @@ -3,7 +3,7 @@ import StructuredQueriesCore @Selection - struct ForeignKey { + package struct ForeignKey { let table: String let from: String let to: String From afb734e78b57ec581949bee61dea6cfa4a0ec9be Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 5 Sep 2025 17:12:02 -0700 Subject: [PATCH 4/5] wip --- Sources/SQLiteData/CloudKit/Internal/Pragmas.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift b/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift index 50d1b385..d384415a 100644 --- a/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift +++ b/Sources/SQLiteData/CloudKit/Internal/Pragmas.swift @@ -25,7 +25,7 @@ struct PragmaForeignKeyList { let match: String } -enum ForeignKeyAction: String, QueryBindable { +package enum ForeignKeyAction: String, QueryBindable { case cascade = "CASCADE" case restrict = "RESTRICT" case setDefault = "SET DEFAULT" From 29c2f127af43a5813a2a49d4be084f3b60ea62fe Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 5 Sep 2025 17:56:04 -0700 Subject: [PATCH 5/5] wip --- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- Examples/SyncUps/SyncUpForm.swift | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c108a80b..1f352895 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -123,8 +123,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-structured-queries", "state" : { - "revision" : "adad5c6c5abe0c62f93c573de5be071043f621a8", - "version" : "0.17.0" + "revision" : "49f18c24145a6e061cc581662f468b7c18523b8d", + "version" : "0.18.0" } }, { diff --git a/Examples/SyncUps/SyncUpForm.swift b/Examples/SyncUps/SyncUpForm.swift index 664a2a56..1fd2b915 100644 --- a/Examples/SyncUps/SyncUpForm.swift +++ b/Examples/SyncUps/SyncUpForm.swift @@ -76,11 +76,14 @@ final class SyncUpFormModel: Identifiable { } withErrorReporting { try database.write { db in - let syncUpID = try SyncUp.upsert(syncUp).returning(\.id).fetchOne(db)! + let syncUpID = try SyncUp.upsert { syncUp }.returning(\.id).fetchOne(db)! try Attendee.where { $0.syncUpID == syncUpID }.delete().execute(db) - try Attendee - .insert(attendees.map { Attendee.Draft(name: $0.name, syncUpID: syncUpID) }) - .execute(db) + try Attendee.insert { + for attendee in attendees { + Attendee.Draft(name: attendee.name, syncUpID: syncUpID) + } + } + .execute(db) } } isDismissed = true