diff --git a/Examples/CaseStudies/DynamicQuery.swift b/Examples/CaseStudies/DynamicQuery.swift index bbc4de0c..cdef9c5c 100644 --- a/Examples/CaseStudies/DynamicQuery.swift +++ b/Examples/CaseStudies/DynamicQuery.swift @@ -93,7 +93,7 @@ struct DynamicQueryDemo: SwiftUICaseStudy { return try Value( facts: search.fetchAll(db), searchCount: search.fetchCount(db), - totalCount: Fact.all.fetchCount(db) + totalCount: Fact.fetchCount(db) ) } } diff --git a/Examples/CaseStudies/TransactionDemo.swift b/Examples/CaseStudies/TransactionDemo.swift index 0d8bdc28..dc4404eb 100644 --- a/Examples/CaseStudies/TransactionDemo.swift +++ b/Examples/CaseStudies/TransactionDemo.swift @@ -64,7 +64,7 @@ struct TransactionDemo: SwiftUICaseStudy { func fetch(_ db: Database) throws -> Value { try Value( facts: Fact.order { $0.id.desc() }.fetchAll(db), - count: Fact.all.fetchCount(db) + count: Fact.fetchCount(db) ) } } diff --git a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 587d0c8d..a0d54872 100644 --- a/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/Examples.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "41e7781e6c506773b6af84af513bcd6d3b1be59d635e6c4c4bd89638368e4629", + "originHash" : "c133bf7d10c8ce1e5d6506c3d2f080eac8b4c8c2827044d53a9b925e903564fd", "pins" : [ { "identity" : "combine-schedulers", @@ -73,24 +73,6 @@ "version" : "1.9.4" } }, - { - "identity" : "swift-docc-plugin", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-docc-plugin", - "state" : { - "revision" : "3e4f133a77e644a5812911a0513aeb7288b07d06", - "version" : "1.4.5" - } - }, - { - "identity" : "swift-docc-symbolkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-docc-symbolkit", - "state" : { - "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", - "version" : "1.0.0" - } - }, { "identity" : "swift-identified-collections", "kind" : "remoteSourceControl", diff --git a/Examples/Reminders/ReminderForm.swift b/Examples/Reminders/ReminderForm.swift index b7b70ed3..febebdca 100644 --- a/Examples/Reminders/ReminderForm.swift +++ b/Examples/Reminders/ReminderForm.swift @@ -208,7 +208,7 @@ struct ReminderFormPreview: PreviewProvider { let (remindersList, reminder) = try! prepareDependencies { $0.defaultDatabase = try Reminders.appDatabase() return try $0.defaultDatabase.write { db in - let remindersList = try RemindersList.all.fetchOne(db)! + let remindersList = try RemindersList.fetchOne(db)! return ( remindersList, try Reminder.where { $0.remindersListID.eq(remindersList.id) }.fetchOne(db)! diff --git a/Examples/Reminders/ReminderRow.swift b/Examples/Reminders/ReminderRow.swift index efe509ad..c112eaa3 100644 --- a/Examples/Reminders/ReminderRow.swift +++ b/Examples/Reminders/ReminderRow.swift @@ -162,8 +162,8 @@ struct ReminderRowPreview: PreviewProvider { let _ = try! prepareDependencies { $0.defaultDatabase = try Reminders.appDatabase() try $0.defaultDatabase.read { db in - reminder = try Reminder.all.fetchOne(db) - remindersList = try RemindersList.all.fetchOne(db)! + reminder = try Reminder.fetchOne(db) + remindersList = try RemindersList.fetchOne(db)! } } diff --git a/Examples/Reminders/RemindersDetail.swift b/Examples/Reminders/RemindersDetail.swift index aee49d67..b64d8110 100644 --- a/Examples/Reminders/RemindersDetail.swift +++ b/Examples/Reminders/RemindersDetail.swift @@ -367,8 +367,8 @@ struct RemindersDetailPreview: PreviewProvider { $0.defaultDatabase = try Reminders.appDatabase() return try $0.defaultDatabase.read { db in ( - try RemindersList.all.fetchOne(db)!, - try Tag.all.fetchOne(db)! + try RemindersList.fetchOne(db)!, + try Tag.fetchOne(db)! ) } } diff --git a/Examples/RemindersTests/RemindersDetailsTests.swift b/Examples/RemindersTests/RemindersDetailsTests.swift index 5bec4b80..18ea8fb6 100644 --- a/Examples/RemindersTests/RemindersDetailsTests.swift +++ b/Examples/RemindersTests/RemindersDetailsTests.swift @@ -12,7 +12,7 @@ extension BaseTestSuite { @Dependency(\.defaultDatabase) var database @Test func basics() async throws { - let remindersList = try await database.read { try RemindersList.all.fetchOne($0)! } + let remindersList = try await database.read { try RemindersList.fetchOne($0)! } let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() assertInlineSnapshot(of: model.reminderRows, as: .customDump) { @@ -118,7 +118,7 @@ extension BaseTestSuite { } @Test func ordering() async throws { - let remindersList = try await database.read { try RemindersList.all.fetchOne($0)! } + let remindersList = try await database.read { try RemindersList.fetchOne($0)! } let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() @@ -164,7 +164,7 @@ extension BaseTestSuite { } @Test func showCompleted() async throws { - let remindersList = try await database.read { try RemindersList.all.fetchOne($0)! } + let remindersList = try await database.read { try RemindersList.fetchOne($0)! } let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() @@ -211,7 +211,7 @@ extension BaseTestSuite { } @Test func move() async throws { - let remindersList = try await database.read { try RemindersList.all.fetchOne($0)! } + let remindersList = try await database.read { try RemindersList.fetchOne($0)! } let model = RemindersDetailModel(detailType: .remindersList(remindersList)) try await model.$reminderRows.load() @@ -319,7 +319,7 @@ extension BaseTestSuite { } @Test func tagged() async throws { - let tag = try await database.read { try Tag.find("someday").fetchOne($0)! } + let tag = try await database.read { try Tag.find($0, key: "someday") } let model = RemindersDetailModel(detailType: .tags([tag])) try await model.$reminderRows.load() assertInlineSnapshot(of: model.reminderRows.map(\.reminder.title), as: .customDump) { diff --git a/Examples/SyncUpTests/SyncUpFormTests.swift b/Examples/SyncUpTests/SyncUpFormTests.swift index e428c38e..81eb3dc1 100644 --- a/Examples/SyncUpTests/SyncUpFormTests.swift +++ b/Examples/SyncUpTests/SyncUpFormTests.swift @@ -39,7 +39,7 @@ struct SyncUpFormTests { @Test func updateExisting() async throws { let existingSyncUp = try await database.read { db in - try #require(try SyncUp.all.fetchOne(db)) + try #require(try SyncUp.fetchOne(db)) } let draft = SyncUp.Draft(existingSyncUp) let model = SyncUpFormModel(syncUp: draft) diff --git a/Sources/SQLiteData/Documentation.docc/SQLiteData.md b/Sources/SQLiteData/Documentation.docc/SQLiteData.md index 943a8fa3..543481a1 100644 --- a/Sources/SQLiteData/Documentation.docc/SQLiteData.md +++ b/Sources/SQLiteData/Documentation.docc/SQLiteData.md @@ -300,6 +300,8 @@ with SQLite to take full advantage of GRDB and SQLiteData. - ``StructuredQueriesCore/Statement`` - ``StructuredQueriesCore/SelectStatement`` +- ``StructuredQueriesCore/Table`` +- ``StructuredQueriesCore/PrimaryKeyedTable`` - ``QueryCursor`` ### Observing model data diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/Statement+GRDB.swift b/Sources/SQLiteData/StructuredQueries+GRDB/Statement+GRDB.swift index a9943ed4..c2c990c1 100644 --- a/Sources/SQLiteData/StructuredQueries+GRDB/Statement+GRDB.swift +++ b/Sources/SQLiteData/StructuredQueries+GRDB/Statement+GRDB.swift @@ -186,6 +186,25 @@ extension SelectStatement where QueryValue == (), Joins == () { } } +extension SelectStatement where QueryValue == (), From: PrimaryKeyedTable, Joins == () { + /// Returns a single value fetched from the database for a given primary key. + /// + /// - Parameters + /// - db: A database connection. + /// - primaryKey: A primary key identifying a table row. + /// - Returns: A single value decoded from the database. + @inlinable + public func find( + _ db: Database, + key primaryKey: some QueryExpression + ) throws -> From.QueryOutput { + guard let record = try asSelect().find(primaryKey).fetchOne(db) else { + throw NotFound() + } + return record + } +} + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) extension SelectStatement where QueryValue == () { /// Returns an array of all values fetched from the database. diff --git a/Sources/SQLiteData/StructuredQueries+GRDB/Table+GRDB.swift b/Sources/SQLiteData/StructuredQueries+GRDB/Table+GRDB.swift new file mode 100644 index 00000000..8e55d707 --- /dev/null +++ b/Sources/SQLiteData/StructuredQueries+GRDB/Table+GRDB.swift @@ -0,0 +1,55 @@ +import StructuredQueriesCore + +extension StructuredQueriesCore.Table { + /// Returns an array of all values fetched from the database. + /// + /// - Parameter db: A database connection. + /// - Returns: An array of all values decoded from the database. + @inlinable + public static func fetchAll(_ db: Database) throws -> [QueryOutput] { + try all.fetchAll(db) + } + + /// Returns a single value fetched from the database. + /// + /// - Parameter db: A database connection. + /// - Returns: A single value decoded from the database. + @inlinable + public static func fetchOne(_ db: Database) throws -> QueryOutput? { + try all.fetchOne(db) + } + + /// Returns the number of rows fetched by the query. + /// + /// - Parameter db: A database connection. + /// - Returns: The number of rows fetched by the query. + @inlinable + public static func fetchCount(_ db: Database) throws -> Int { + try all.fetchCount(db) + } + + /// Returns a cursor to all values fetched from the database. + /// + /// - Parameter db: A database connection. + /// - Returns: A cursor to all values decoded from the database. + @inlinable + public static func fetchCursor(_ db: Database) throws -> QueryCursor { + try all.fetchCursor(db) + } +} + +extension StructuredQueriesCore.PrimaryKeyedTable { + /// Returns a single value fetched from the database for a given primary key. + /// + /// - Parameters + /// - db: A database connection. + /// - primaryKey: A primary key identifying a table row. + /// - Returns: A single value decoded from the database. + @inlinable + public static func find( + _ db: Database, + key primaryKey: some QueryExpression + ) throws -> QueryOutput { + try all.find(db, key: primaryKey) + } +}