Skip to content

Commit

Permalink
QueryInterfaceRequest.deleteAndFetchIds
Browse files Browse the repository at this point in the history
  • Loading branch information
groue committed Jun 5, 2024
1 parent b312d4c commit bb789e3
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ GRDB adheres to [Semantic Versioning](https://semver.org/), with one exception:

---

## Next Release

- **New**: Added `QueryInterfaceRequest.deleteAndFetchIds(_:)` which returns the set of deleted ids.

## 6.27.0

Released April 21, 2024
Expand Down
71 changes: 71 additions & 0 deletions GRDB/QueryInterface/Request/QueryInterfaceRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
/// ### Batch Delete
///
/// - ``deleteAll(_:)``
/// - ``deleteAndFetchIds(_:)``
/// - ``deleteAndFetchCursor(_:)``
/// - ``deleteAndFetchAll(_:)``
/// - ``deleteAndFetchSet(_:)``
Expand Down Expand Up @@ -625,6 +626,41 @@ extension QueryInterfaceRequest {
{
try Set(deleteAndFetchCursor(db))
}

/// Executes a `DELETE RETURNING` statement and returns the set of
/// deleted ids.
///
/// For example:
///
/// ```swift
/// // Fetch the ids of deleted players
/// // DELETE FROM player RETURNING id
/// let request = Player.all()
/// let deletedPlayerIds = try request.deleteAndFetchIds(db)
/// ```
///
/// - important: Make sure you check the documentation of the `RETURNING`
/// clause, which describes important limitations and caveats:
/// <https://www.sqlite.org/lang_returning.html#limitations_and_caveats>.
///
/// - parameter db: A database connection.
/// - returns: A set of deleted ids.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // Identifiable
public func deleteAndFetchIds(_ db: Database)
throws -> Set<RowDecoder.ID>
where RowDecoder: TableRecord & Identifiable,
RowDecoder.ID: Hashable & DatabaseValueConvertible & StatementColumnConvertible
{
let primaryKey = try db.primaryKey(RowDecoder.databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Fetching id requires a single-column primary key in the table \(databaseTableName)")

let statement = try deleteAndFetchStatement(db, selection: [Column(primaryKey.columns[0])])

return try RowDecoder.ID.fetchSet(statement)
}
#else
/// Returns a `DELETE RETURNING` prepared statement.
///
Expand Down Expand Up @@ -741,6 +777,41 @@ extension QueryInterfaceRequest {
{
try Set(deleteAndFetchCursor(db))
}

/// Executes a `DELETE RETURNING` statement and returns the set of
/// deleted ids.
///
/// For example:
///
/// ```swift
/// // Fetch the ids of deleted players
/// // DELETE FROM player RETURNING id
/// let request = Player.all()
/// let deletedPlayerIds = try request.deleteAndFetchIds(db)
/// ```
///
/// - important: Make sure you check the documentation of the `RETURNING`
/// clause, which describes important limitations and caveats:
/// <https://www.sqlite.org/lang_returning.html#limitations_and_caveats>.
///
/// - parameter db: A database connection.
/// - returns: A set of deleted ids.
/// - throws: A ``DatabaseError`` whenever an SQLite error occurs.
@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) // SQLite 3.35.0+
public func deleteAndFetchIds(_ db: Database)
throws -> Set<RowDecoder.ID>
where RowDecoder: TableRecord & Identifiable,
RowDecoder.ID: Hashable & DatabaseValueConvertible & StatementColumnConvertible
{
let primaryKey = try db.primaryKey(RowDecoder.databaseTableName)
GRDBPrecondition(
primaryKey.columns.count == 1,
"Fetching id requires a single-column primary key in the table \(databaseTableName)")

let statement = try deleteAndFetchStatement(db, selection: [Column(primaryKey.columns[0])])

return try RowDecoder.ID.fetchSet(statement)
}
#endif
}

Expand Down
24 changes: 24 additions & 0 deletions Tests/GRDBTests/TableRecordDeleteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,30 @@ class TableRecordDeleteTests: GRDBTestCase {
}
}

@available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) // Identifiable
func testRequestDeleteAndFetchIds() throws {
#if GRDBCUSTOMSQLITE || GRDBCIPHER
guard sqlite3_libversion_number() >= 3035000 else {
throw XCTSkip("RETURNING clause is not available")
}
#else
guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else {
throw XCTSkip("RETURNING clause is not available")
}
#endif

let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
try Person(id: 1, name: "Arthur", email: "[email protected]").insert(db)
try Person(id: 2, name: "Barbara", email: "[email protected]").insert(db)
try Person(id: 3, name: "Craig", email: "[email protected]").insert(db)

let request = Person.filter(Column("id") != 2)
let deletedIds = try request.deleteAndFetchIds(db)
XCTAssertEqual(deletedIds, [1, 3])
}
}

func testJoinedRequestDeleteAll() throws {
try makeDatabaseQueue().inDatabase { db in
struct Player: MutablePersistableRecord {
Expand Down

0 comments on commit bb789e3

Please sign in to comment.