Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions Examples/SyncUps/SyncUpForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +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",
from: "0.18.0",
traits: [
.trait(name: "StructuredQueriesTagged", condition: .when(traits: ["SQLiteDataTagged"]))
]
Expand Down
2 changes: 1 addition & 1 deletion Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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", from: "0.18.0"),
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.5.0"),
],
targets: [
Expand Down
57 changes: 5 additions & 52 deletions Sources/SQLiteData/CloudKit/Internal/ForeignKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,13 @@
import Foundation
import StructuredQueriesCore

package struct ForeignKey: QueryDecodable, QueryRepresentable {
typealias QueryValue = Self

@Selection
package struct ForeignKey {
let table: String
let from: String
let to: String
let onUpdate: Action
let onDelete: Action
let notnull: Bool

package 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<Self> {
#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
64 changes: 64 additions & 0 deletions Sources/SQLiteData/CloudKit/Internal/Pragmas.swift
Original file line number Diff line number Diff line change
@@ -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<Base: Table> {
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
}

package 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<Base: Table> {
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<Base: Table> {
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
}
38 changes: 2 additions & 36 deletions Sources/SQLiteData/CloudKit/Internal/TableInfo.swift
Original file line number Diff line number Diff line change
@@ -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<Self> {
#sql(
"""
SELECT \(columns) FROM pragma_table_info(\(bind: tableName))
""",
as: Self.self
)
}

static var columns: QueryFragment {
"""
"dflt_value", "pk", "name", "notnull", "type"
"""
}
}
107 changes: 61 additions & 46 deletions Sources/SQLiteData/CloudKit/SyncEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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: StructuredQueriesCore.Table>(_: T.Type) throws -> (String, [ForeignKey]) {
(
table.tableName,
try PragmaForeignKeyList<T>
.join(PragmaTableInfo<T>.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)
}
}
)
Expand Down Expand Up @@ -257,14 +272,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
Expand Down Expand Up @@ -366,13 +376,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: StructuredQueriesCore.Table>(_: T.Type) throws -> RecordType {
try RecordType(
tableName: schema.name,
schema: sql,
tableInfo: Set(
PragmaTableInfo<T>
.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(
Expand Down Expand Up @@ -1747,13 +1772,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(
Expand Down Expand Up @@ -1847,23 +1866,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: StructuredQueriesCore.Table>(_: T.Type) throws {
let columnsWithUniqueConstraints = try PragmaIndexList<T>
.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)
}
}
}
Expand Down Expand Up @@ -1891,13 +1908,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: StructuredQueriesCore.Table>(_: T.Type) throws -> [String] {
try PragmaForeignKeyList<T>.select(\.table)
.fetchAll(db)
}
let toTables = try open(table)
for toTable in toTables {
guard let toTableType = tablesByName[toTable]
else { continue }
Expand Down
Loading