Skip to content

Commit 609c4d8

Browse files
committed
chore: add transaction and watch error handling
1 parent b8fe242 commit 609c4d8

File tree

5 files changed

+155
-50
lines changed

5 files changed

+155
-50
lines changed

Demo/PowerSyncExample/Components/TodoListView.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct TodoListView: View {
2828
ForEach(todos) { todo in
2929
TodoListRow(todo: todo) {
3030
Task {
31-
await toggleCompletion(of: todo)
31+
try await toggleCompletion(of: todo)
3232
}
3333
}
3434
}

Demo/PowerSyncExample/PowerSync/SystemManager.swift

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,19 +34,23 @@ class SystemManager {
3434
}
3535

3636
func watchLists(_ callback: @escaping (_ lists: [ListContent]) -> Void ) async {
37-
for await lists in self.db.watch<[ListContent]>(
38-
sql: "SELECT * FROM \(LISTS_TABLE)",
39-
parameters: [],
40-
mapper: { cursor in
41-
ListContent(
42-
id: try cursor.getString(name: "id"),
43-
name: try cursor.getString(name: "name"),
44-
createdAt: try cursor.getString(name: "created_at"),
45-
ownerId: try cursor.getString(name: "owner_id")
46-
)
37+
do {
38+
for try await lists in try self.db.watch<ListContent>(
39+
sql: "SELECT * FROM \(LISTS_TABLE)",
40+
parameters: [],
41+
mapper: { cursor in
42+
try ListContent(
43+
id: cursor.getString(name: "id"),
44+
name: cursor.getString(name: "name"),
45+
createdAt: cursor.getString(name: "created_at"),
46+
ownerId: cursor.getString(name: "owner_id")
47+
)
48+
}
49+
) {
50+
callback(lists)
4751
}
48-
) {
49-
callback(lists)
52+
} catch {
53+
print("Error in watch: \(error)")
5054
}
5155
}
5256

@@ -59,11 +63,11 @@ class SystemManager {
5963

6064
func deleteList(id: String) async throws {
6165
_ = try await db.writeTransaction(callback: { transaction in
62-
_ = transaction.execute(
66+
_ = try transaction.execute(
6367
sql: "DELETE FROM \(LISTS_TABLE) WHERE id = ?",
6468
parameters: [id]
6569
)
66-
_ = transaction.execute(
70+
_ = try transaction.execute(
6771
sql: "DELETE FROM \(TODOS_TABLE) WHERE list_id = ?",
6872
parameters: [id]
6973
)
@@ -72,24 +76,28 @@ class SystemManager {
7276
}
7377

7478
func watchTodos(_ listId: String, _ callback: @escaping (_ todos: [Todo]) -> Void ) async {
75-
for await todos in self.db.watch(
76-
sql: "SELECT * FROM \(TODOS_TABLE) WHERE list_id = ?",
77-
parameters: [listId],
78-
mapper: { cursor in
79-
return Todo(
80-
id: try cursor.getString(name: "id"),
81-
listId: try cursor.getString(name: "list_id"),
82-
photoId: try cursor.getStringOptional(name: "photo_id"),
83-
description: try cursor.getString(name: "description"),
84-
isComplete: try cursor.getBoolean(name: "completed"),
85-
createdAt: try cursor.getString(name: "created_at"),
86-
completedAt: try cursor.getStringOptional(name: "completed_at"),
87-
createdBy: try cursor.getStringOptional(name: "created_by"),
88-
completedBy: try cursor.getStringOptional(name: "completed_by")
89-
)
79+
do {
80+
for try await todos in try self.db.watch(
81+
sql: "SELECT * FROM \(TODOS_TABLE) WHERE list_id = ?",
82+
parameters: [listId],
83+
mapper: { cursor in
84+
try Todo(
85+
id: cursor.getString(name: "id"),
86+
listId: cursor.getString(name: "list_id"),
87+
photoId: cursor.getStringOptional(name: "photo_id"),
88+
description: cursor.getString(name: "description"),
89+
isComplete: cursor.getBoolean(name: "completed"),
90+
createdAt: cursor.getString(name: "created_at"),
91+
completedAt: cursor.getStringOptional(name: "completed_at"),
92+
createdBy: cursor.getStringOptional(name: "created_by"),
93+
completedBy: cursor.getStringOptional(name: "completed_by")
94+
)
95+
}
96+
) {
97+
callback(todos)
9098
}
91-
) {
92-
callback(todos)
99+
} catch {
100+
print("Error in watch: \(error)")
93101
}
94102
}
95103

@@ -117,7 +125,7 @@ class SystemManager {
117125

118126
func deleteTodo(id: String) async throws {
119127
_ = try await db.writeTransaction(callback: { transaction in
120-
transaction.execute(
128+
try transaction.execute(
121129
sql: "DELETE FROM \(TODOS_TABLE) WHERE id = ?",
122130
parameters: [id]
123131
)

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,42 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
194194
}
195195
}
196196

197-
public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {
198-
return try await kotlinDatabase.writeTransaction(callback: callback) as! R
197+
public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R {
198+
var err: Error? = nil
199+
let result = try await kotlinDatabase.writeTransaction { transaction in
200+
do {
201+
let res = try callback(transaction)
202+
return res as R
203+
} catch {
204+
err = error
205+
return TransactionResponse.rollback
206+
}
207+
}
208+
209+
if(err != nil) {
210+
throw err!
211+
}
212+
213+
return result as! R
214+
}
215+
216+
public func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R {
217+
var err: Error? = nil
218+
let result = try await kotlinDatabase.readTransaction { transaction in
219+
do {
220+
let res = try callback(transaction)
221+
return res as R
222+
} catch {
223+
err = error
224+
return TransactionResponse.rollback
225+
}
226+
}
227+
228+
if(err != nil) {
229+
throw err!
230+
}
231+
232+
return result as! R
199233
}
200234

201235
public func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {

Sources/PowerSync/QueriesProtocol.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ public protocol Queries {
7070
) throws -> AsyncThrowingStream<[RowType], Error>
7171

7272
/// Execute a write transaction with the given callback
73-
func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R
73+
func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R
7474

7575
/// Execute a read transaction with the given callback
76-
func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R
76+
func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R
7777
}
7878

7979
extension Queries {

Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
2626
database = nil
2727
try await super.tearDown()
2828
}
29-
29+
3030
func testExecuteError() async throws {
3131
do {
3232
_ = try await database.execute(
@@ -41,7 +41,7 @@ no such table: usersfail
4141
""")
4242
}
4343
}
44-
44+
4545
func testInsertAndGet() async throws {
4646
_ = try await database.execute(
4747
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
@@ -63,7 +63,7 @@ no such table: usersfail
6363
XCTAssertEqual(user.1, "Test User")
6464
XCTAssertEqual(user.2, "[email protected]")
6565
}
66-
66+
6767
func testGetError() async throws {
6868
do {
6969
let _ = try await database.get(
@@ -109,7 +109,7 @@ no such table: usersfail
109109

110110
XCTAssertEqual(existing, "Test User")
111111
}
112-
112+
113113
func testGetOptionalError() async throws {
114114
do {
115115
let _ = try await database.getOptional(
@@ -153,7 +153,7 @@ no such table: usersfail
153153
XCTAssertEqual(users[1].0, "2")
154154
XCTAssertEqual(users[1].1, "User 2")
155155
}
156-
156+
157157
func testGetAllError() async throws {
158158
do {
159159
let _ = try await database.getAll(
@@ -230,7 +230,7 @@ no such table: usersfail
230230
XCTAssertEqual(finalResults.count, 2)
231231
XCTAssertEqual(finalResults[1], ["User 1", "User 2"])
232232
}
233-
233+
234234
func testWatchError() async throws {
235235
do {
236236
let stream = try database.watch(
@@ -239,12 +239,12 @@ no such table: usersfail
239239
) { cursor in
240240
cursor.getString(index: 0)!
241241
}
242-
242+
243243
// Actually consume the stream to trigger the error
244244
for try await _ in stream {
245245
XCTFail("Should not receive any values")
246246
}
247-
247+
248248
XCTFail("Expected an error to be thrown")
249249
} catch {
250250
XCTAssertEqual(error.localizedDescription, """
@@ -256,12 +256,12 @@ no such table: usersfail
256256

257257
func testWriteTransaction() async throws {
258258
_ = try await database.writeTransaction { transaction in
259-
_ = transaction.execute(
259+
_ = try transaction.execute(
260260
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
261261
parameters: ["1", "Test User", "[email protected]"]
262262
)
263263

264-
_ = transaction.execute(
264+
_ = try transaction.execute(
265265
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
266266
parameters: ["2", "Test User 2", "[email protected]"]
267267
)
@@ -283,12 +283,12 @@ no such table: usersfail
283283

284284
_ = try await database.writeTransaction { transaction in
285285
for i in 1...loopCount {
286-
_ = transaction.execute(
286+
_ = try transaction.execute(
287287
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
288288
parameters: [String(i), "Test User \(i)", "test\(i)@example.com"]
289289
)
290290

291-
_ = transaction.execute(
291+
_ = try transaction.execute(
292292
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
293293
parameters: [String(i*10000), "Test User \(i)-2", "test\(i)[email protected]"]
294294
)
@@ -305,6 +305,51 @@ no such table: usersfail
305305
XCTAssertEqual(result as! Int, 2 * loopCount)
306306
}
307307

308+
func testWriteTransactionError() async throws {
309+
do {
310+
_ = try await database.writeTransaction { transaction in
311+
_ = try transaction.execute(
312+
sql: "INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)",
313+
parameters: ["2", "Test User 2", "[email protected]"]
314+
)
315+
}
316+
} catch {
317+
XCTAssertEqual(error.localizedDescription, """
318+
error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)
319+
no such table: usersfail
320+
""")
321+
}
322+
}
323+
324+
func testWriteTransactionErrorPerformsRollBack() async throws {
325+
do {
326+
_ = try await database.writeTransaction { transaction in
327+
_ = try transaction.execute(
328+
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
329+
parameters: ["1", "Test User", "[email protected]"]
330+
)
331+
332+
_ = try transaction.execute(
333+
sql: "INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)",
334+
parameters: ["2", "Test User 2", "[email protected]"]
335+
)
336+
}
337+
} catch {
338+
XCTAssertEqual(error.localizedDescription, """
339+
error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)
340+
no such table: usersfail
341+
""")
342+
}
343+
344+
let result = try await database.getOptional(
345+
sql: "SELECT COUNT(*) FROM users",
346+
parameters: []
347+
) { cursor in try cursor.getLong(index: 0)
348+
}
349+
350+
XCTAssertEqual(result, 0)
351+
}
352+
308353
func testReadTransaction() async throws {
309354
_ = try await database.execute(
310355
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
@@ -313,7 +358,7 @@ no such table: usersfail
313358

314359

315360
_ = try await database.readTransaction { transaction in
316-
let result = transaction.get(
361+
let result = try transaction.get(
317362
sql: "SELECT COUNT(*) FROM users",
318363
parameters: []
319364
) { cursor in
@@ -323,4 +368,22 @@ no such table: usersfail
323368
XCTAssertEqual(result as! Int, 1)
324369
}
325370
}
371+
372+
func testReadTransactionError() async throws {
373+
do {
374+
_ = try await database.readTransaction { transaction in
375+
let result = try transaction.get(
376+
sql: "SELECT COUNT(*) FROM usersfail",
377+
parameters: []
378+
) { cursor in
379+
cursor.getLong(index: 0)
380+
}
381+
}
382+
} catch {
383+
XCTAssertEqual(error.localizedDescription, """
384+
error while compiling: SELECT COUNT(*) FROM usersfail
385+
no such table: usersfail
386+
""")
387+
}
388+
}
326389
}

0 commit comments

Comments
 (0)