Skip to content

Commit

Permalink
[BREAKING] insertAndFetch, saveAndFetch, and updateAndFetch no longer…
Browse files Browse the repository at this point in the history
… return optionals.

Those optionals were just an annoyance. Now, nil results are replaced with `RecordError.recordNotFound`. Such an error is only expected with the IGNORE conflict policy.
  • Loading branch information
groue committed Aug 24, 2024
1 parent e75eda0 commit 39ed103
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 147 deletions.
66 changes: 36 additions & 30 deletions GRDB/Record/MutablePersistableRecord+Insert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ extension MutablePersistableRecord {

extension MutablePersistableRecord {
#if GRDBCUSTOMSQLITE || GRDBCIPHER
// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` statement, and returns a new record built
/// from the inserted row.
///
Expand All @@ -108,22 +107,22 @@ extension MutablePersistableRecord {
/// - parameter conflictResolution: A policy for conflict resolution. If
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - returns: The inserted record, if any. The result can be nil when the
/// conflict policy is `IGNORE`.
/// - returns: The inserted record.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the insertion failed due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
public func insertAndFetch(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil)
throws -> Self?
throws -> Self
where Self: FetchableRecord
{
var result = self
return try result.insertAndFetch(db, onConflict: conflictResolution, as: Self.self)
}

// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` statement, and returns a new record built
/// from the inserted row.
///
Expand Down Expand Up @@ -161,11 +160,10 @@ extension MutablePersistableRecord {
/// var partialPlayer = PartialPlayer(name: "Alice")
///
/// // INSERT INTO player (name) VALUES ('Alice') RETURNING *
/// if let player = try partialPlayer.insertAndFetch(db, as: FullPlayer.self) {
/// print(player.id) // The inserted id
/// print(player.name) // The inserted name
/// print(player.score) // The default score
/// }
/// let player = try partialPlayer.insertAndFetch(db, as: FullPlayer.self)
/// print(player.id) // The inserted id
/// print(player.name) // The inserted name
/// print(player.score) // The default score
/// }
/// ```
///
Expand All @@ -174,19 +172,24 @@ extension MutablePersistableRecord {
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - parameter returnedType: The type of the returned record.
/// - returns: A record of type `returnedType`, if any. The result can be
/// nil when the conflict policy is `IGNORE`.
/// - returns: A record of type `returnedType`.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the insertion failed due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
public mutating func insertAndFetch<T: FetchableRecord & TableRecord>(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil,
as returnedType: T.Type)
throws -> T?
throws -> T
{
try insertAndFetch(db, onConflict: conflictResolution, selection: T.databaseSelection) {
try T.fetchOne($0)
let record = self
return try insertAndFetch(db, onConflict: conflictResolution, selection: T.databaseSelection) {
if let result = try T.fetchOne($0) {
return result
}
throw record.recordNotFound(db)
}
}

Expand Down Expand Up @@ -266,7 +269,6 @@ extension MutablePersistableRecord {
return success.returned
}
#else
// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` statement, and returns a new record built
/// from the inserted row.
///
Expand All @@ -283,23 +285,23 @@ extension MutablePersistableRecord {
/// - parameter conflictResolution: A policy for conflict resolution. If
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - returns: The inserted record, if any. The result can be nil when the
/// conflict policy is `IGNORE`.
/// - returns: The inserted record.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the insertion failed due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) // SQLite 3.35.0+
public func insertAndFetch(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil)
throws -> Self?
throws -> Self
where Self: FetchableRecord
{
var result = self
return try result.insertAndFetch(db, onConflict: conflictResolution, as: Self.self)
}

// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` statement, and returns a new record built
/// from the inserted row.
///
Expand Down Expand Up @@ -337,11 +339,10 @@ extension MutablePersistableRecord {
/// var partialPlayer = PartialPlayer(name: "Alice")
///
/// // INSERT INTO player (name) VALUES ('Alice') RETURNING *
/// if let player = try partialPlayer.insertAndFetch(db, as: FullPlayer.self) {
/// print(player.id) // The inserted id
/// print(player.name) // The inserted name
/// print(player.score) // The default score
/// }
/// let player = try partialPlayer.insertAndFetch(db, as: FullPlayer.self)
/// print(player.id) // The inserted id
/// print(player.name) // The inserted name
/// print(player.score) // The default score
/// }
/// ```
///
Expand All @@ -350,20 +351,25 @@ extension MutablePersistableRecord {
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - parameter returnedType: The type of the returned record.
/// - returns: A record of type `returnedType`, if any. The result can be
/// nil when the conflict policy is `IGNORE`.
/// - returns: A record of type `returnedType`.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the insertion failed due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) // SQLite 3.35.0+
public mutating func insertAndFetch<T: FetchableRecord & TableRecord>(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil,
as returnedType: T.Type)
throws -> T?
throws -> T
{
try insertAndFetch(db, onConflict: conflictResolution, selection: T.databaseSelection) {
try T.fetchOne($0)
let record = self
return try insertAndFetch(db, onConflict: conflictResolution, selection: T.databaseSelection) {
if let result = try T.fetchOne($0) {
return result
}
throw record.recordNotFound(db)
}
}

Expand Down
48 changes: 28 additions & 20 deletions GRDB/Record/MutablePersistableRecord+Save.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ extension MutablePersistableRecord {

extension MutablePersistableRecord {
#if GRDBCUSTOMSQLITE || GRDBCIPHER
// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` or `UPDATE RETURNING` statement, and
/// returns a new record built from the saved row.
///
Expand All @@ -107,22 +106,22 @@ extension MutablePersistableRecord {
/// - parameter conflictResolution: A policy for conflict resolution. If
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - returns: The saved record. The result can be nil when the
/// conflict policy is `IGNORE`.
/// - returns: The saved record.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the database changes fail due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
public func saveAndFetch(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil)
throws -> Self?
throws -> Self
where Self: FetchableRecord
{
var result = self
return try result.saveAndFetch(db, onConflict: conflictResolution, as: Self.self)
}

// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` or `UPDATE RETURNING` statement, and
/// returns a new record built from the saved row.
///
Expand All @@ -135,26 +134,31 @@ extension MutablePersistableRecord {
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - parameter returnedType: The type of the returned record.
/// - returns: A record of type `returnedType`. The result can be nil when
/// the conflict policy is `IGNORE`.
/// - returns: A record of type `returnedType`.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the database changes fail due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
public mutating func saveAndFetch<T: FetchableRecord & TableRecord>(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil,
as returnedType: T.Type)
throws -> T?
throws -> T
{
try willSave(db)

var success: (saved: PersistenceSuccess, returned: T?)?
var success: (saved: PersistenceSuccess, returned: T)?
try aroundSave(db) {
let record = self
success = try updateOrInsertAndFetchWithCallbacks(
db, onConflict: conflictResolution,
selection: T.databaseSelection,
fetch: {
try T.fetchOne($0)
if let result = try T.fetchOne($0) {
return result
}
throw record.recordNotFound(db)
})
return success!.saved
}
Expand Down Expand Up @@ -211,7 +215,6 @@ extension MutablePersistableRecord {
return success.returned
}
#else
// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` or `UPDATE RETURNING` statement, and
/// returns a new record built from the saved row.
///
Expand All @@ -231,23 +234,23 @@ extension MutablePersistableRecord {
/// - parameter conflictResolution: A policy for conflict resolution. If
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - returns: The saved record. The result can be nil when the
/// conflict policy is `IGNORE`.
/// - returns: The saved record.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the database changes fail due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) // SQLite 3.35.0+
public func saveAndFetch(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil)
throws -> Self?
throws -> Self
where Self: FetchableRecord
{
var result = self
return try result.saveAndFetch(db, onConflict: conflictResolution, as: Self.self)
}

// TODO: GRDB7 make it unable to return an optional
/// Executes an `INSERT RETURNING` or `UPDATE RETURNING` statement, and
/// returns a new record built from the saved row.
///
Expand All @@ -260,27 +263,32 @@ extension MutablePersistableRecord {
/// nil, <doc:/MutablePersistableRecord/persistenceConflictPolicy-1isyv>
/// is used.
/// - parameter returnedType: The type of the returned record.
/// - returns: A record of type `returnedType`. The result can be nil when
/// the conflict policy is `IGNORE`.
/// - returns: A record of type `returnedType`.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs, or any
/// error thrown by the persistence callbacks defined by the record type.
/// ``RecordError/recordNotFound(databaseTableName:key:)`` can be
/// thrown if the database changes fail due to the IGNORE conflict policy.
@inlinable // allow specialization so that empty callbacks are removed
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) // SQLite 3.35.0+
public mutating func saveAndFetch<T: FetchableRecord & TableRecord>(
_ db: Database,
onConflict conflictResolution: Database.ConflictResolution? = nil,
as returnedType: T.Type)
throws -> T?
throws -> T
{
try willSave(db)

var success: (saved: PersistenceSuccess, returned: T?)?
var success: (saved: PersistenceSuccess, returned: T)?
try aroundSave(db) {
let record = self
success = try updateOrInsertAndFetchWithCallbacks(
db, onConflict: conflictResolution,
selection: T.databaseSelection,
fetch: {
try T.fetchOne($0)
if let result = try T.fetchOne($0) {
return result
}
throw record.recordNotFound(db)
})
return success!.saved
}
Expand Down
Loading

0 comments on commit 39ed103

Please sign in to comment.