Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f50f8dd

Browse files
authoredFeb 13, 2025··
chore: make execute, watch and transaction functions throwable in swift (#21)
* chore: make execute and watch throwable in swift * chore: add transaction and watch error handling * chore: update package
1 parent af67832 commit f50f8dd

File tree

9 files changed

+297
-80
lines changed

9 files changed

+297
-80
lines changed
 

‎CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# Changelog
22

3+
## 1.0.0-Beta.6
4+
5+
* BREAKING CHANGE: `watch` queries are now throwable and therefore will need to be accompanied by a `try` e.g.
6+
7+
```swift
8+
try database.watch()
9+
```
10+
11+
* BREAKING CHANGE: `transaction` functions are now throwable and therefore will need to be accompanied by a `try` e.g.
12+
13+
```swift
14+
try await database.writeTransaction { transaction in
15+
try transaction.execute(...)
16+
}
17+
```
18+
* Allow `execute` errors to be handled
19+
* `userId` is now set to `nil` by default and therefore it is no longer required to be set to `nil` when instantiating `PowerSyncCredentials` and can therefore be left out.
20+
321
## 1.0.0-Beta.5
422

523
* Implement improvements to errors originating in Kotlin so that they can be handled in Swift

‎Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
"kind" : "remoteSourceControl",
1616
"location" : "https://github.com/powersync-ja/powersync-kotlin.git",
1717
"state" : {
18-
"revision" : "61d195816585f30260181dcbd157bf1660c9ac4e",
19-
"version" : "1.0.0-BETA22.0"
18+
"revision" : "203db74889df8a20e3c6ac38aede6b0186d2e3b5",
19+
"version" : "1.0.0-BETA23.0"
2020
}
2121
},
2222
{

‎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
)

‎Package.resolved

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"kind" : "remoteSourceControl",
66
"location" : "https://github.com/powersync-ja/powersync-kotlin.git",
77
"state" : {
8-
"revision" : "61d195816585f30260181dcbd157bf1660c9ac4e",
9-
"version" : "1.0.0-BETA22.0"
8+
"revision" : "203db74889df8a20e3c6ac38aede6b0186d2e3b5",
9+
"version" : "1.0.0-BETA23.0"
1010
}
1111
},
1212
{

‎Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let package = Package(
1616
targets: ["PowerSync"]),
1717
],
1818
dependencies: [
19-
.package(url: "https://github.com/powersync-ja/powersync-kotlin.git", exact: "1.0.0-BETA22.0"),
19+
.package(url: "https://github.com/powersync-ja/powersync-kotlin.git", exact: "1.0.0-BETA23.0"),
2020
.package(url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "0.3.9"..<"0.4.0")
2121
],
2222
targets: [

‎Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
7979
mapper: mapper
8080
) as! RowType
8181
}
82-
82+
8383
func get<RowType>(
8484
sql: String,
8585
parameters: [Any]?,
@@ -93,7 +93,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
9393
}
9494
) as! RowType
9595
}
96-
96+
9797
func getAll<RowType>(
9898
sql: String,
9999
parameters: [Any]?,
@@ -105,7 +105,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
105105
mapper: mapper
106106
) as! [RowType]
107107
}
108-
108+
109109
func getAll<RowType>(
110110
sql: String,
111111
parameters: [Any]?,
@@ -131,7 +131,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
131131
mapper: mapper
132132
) as! RowType?
133133
}
134-
134+
135135
func getOptional<RowType>(
136136
sql: String,
137137
parameters: [Any]?,
@@ -150,48 +150,75 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
150150
sql: String,
151151
parameters: [Any]?,
152152
mapper: @escaping (SqlCursor) -> RowType
153-
) -> AsyncStream<[RowType]> {
154-
AsyncStream { continuation in
153+
) throws -> AsyncThrowingStream<[RowType], Error> {
154+
AsyncThrowingStream { continuation in
155155
Task {
156-
for await values in try self.kotlinDatabase.watch(
157-
sql: sql,
158-
parameters: parameters,
159-
mapper: mapper
160-
) {
161-
continuation.yield(values as! [RowType])
156+
do {
157+
for await values in try self.kotlinDatabase.watch(
158+
sql: sql,
159+
parameters: parameters,
160+
mapper: mapper
161+
) {
162+
continuation.yield(values as! [RowType])
163+
}
164+
continuation.finish()
165+
} catch {
166+
continuation.finish(throwing: error)
162167
}
163-
continuation.finish()
164168
}
165169
}
166170
}
167-
171+
168172
func watch<RowType>(
169173
sql: String,
170174
parameters: [Any]?,
171175
mapper: @escaping (SqlCursor) throws -> RowType
172-
) -> AsyncStream<[RowType]> {
173-
AsyncStream { continuation in
176+
) throws -> AsyncThrowingStream<[RowType], Error> {
177+
AsyncThrowingStream { continuation in
174178
Task {
175-
for await values in try self.kotlinDatabase.watch(
176-
sql: sql,
177-
parameters: parameters,
178-
mapper: { cursor in
179-
try! mapper(cursor)
179+
do {
180+
for await values in try self.kotlinDatabase.watch(
181+
sql: sql,
182+
parameters: parameters,
183+
mapper: { cursor in
184+
try! mapper(cursor)
185+
}
186+
) {
187+
continuation.yield(values as! [RowType])
180188
}
181-
) {
182-
continuation.yield(values as! [RowType])
189+
continuation.finish()
190+
} catch {
191+
continuation.finish(throwing: error)
183192
}
184-
continuation.finish()
185193
}
186194
}
187195
}
188-
189-
public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {
190-
return try await kotlinDatabase.writeTransaction(callback: callback) as! R
196+
197+
public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R {
198+
return try await kotlinDatabase.writeTransaction(callback: TransactionCallback(callback: callback)) as! R
199+
}
200+
201+
public func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) throws -> R) async throws -> R {
202+
return try await kotlinDatabase.readTransaction(callback: TransactionCallback(callback: callback)) as! R
191203
}
204+
}
205+
206+
class TransactionCallback<R>: PowerSyncKotlin.ThrowableTransactionCallback {
207+
let callback: (PowerSyncTransaction) throws -> R
192208

193-
public func readTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {
194-
return try await kotlinDatabase.readTransaction(callback: callback) as! R
209+
init(callback: @escaping (PowerSyncTransaction) throws -> R) {
210+
self.callback = callback
211+
}
212+
213+
func execute(transaction: PowerSyncKotlin.PowerSyncTransaction) throws -> Any{
214+
do {
215+
return try callback(transaction)
216+
} catch let error {
217+
return PowerSyncKotlin.PowerSyncException(
218+
message: error.localizedDescription,
219+
cause: PowerSyncKotlin.KotlinThrowable(message: error.localizedDescription)
220+
)
221+
}
195222
}
196223
}
197224

‎Sources/PowerSync/QueriesProtocol.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,21 @@ public protocol Queries {
5959
sql: String,
6060
parameters: [Any]?,
6161
mapper: @escaping (SqlCursor) -> RowType
62-
) -> AsyncStream<[RowType]>
62+
) throws -> AsyncThrowingStream<[RowType], Error>
6363

6464
/// Execute a read-only (SELECT) query every time the source tables are modified
6565
/// and return the results as an array in a Publisher.
6666
func watch<RowType>(
6767
sql: String,
6868
parameters: [Any]?,
6969
mapper: @escaping (SqlCursor) throws -> RowType
70-
) -> AsyncStream<[RowType]>
70+
) 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 {
@@ -105,7 +105,7 @@ extension Queries {
105105
public func watch<RowType>(
106106
_ sql: String,
107107
mapper: @escaping (SqlCursor) -> RowType
108-
) -> AsyncStream<[RowType]> {
109-
return watch(sql: sql, parameters: [], mapper: mapper)
108+
) throws -> AsyncThrowingStream<[RowType], Error> {
109+
return try watch(sql: sql, parameters: [], mapper: mapper)
110110
}
111111
}

‎Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift

Lines changed: 171 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
2727
try await super.tearDown()
2828
}
2929

30+
func testExecuteError() async throws {
31+
do {
32+
_ = try await database.execute(
33+
sql: "INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)",
34+
parameters: ["1", "Test User", "test@example.com"]
35+
)
36+
XCTFail("Expected an error to be thrown")
37+
} catch {
38+
XCTAssertEqual(error.localizedDescription, """
39+
error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)
40+
no such table: usersfail
41+
""")
42+
}
43+
}
44+
3045
func testInsertAndGet() async throws {
3146
_ = try await database.execute(
3247
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
@@ -49,6 +64,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
4964
XCTAssertEqual(user.2, "test@example.com")
5065
}
5166

67+
func testGetError() async throws {
68+
do {
69+
let _ = try await database.get(
70+
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
71+
parameters: ["1"]
72+
) { cursor in
73+
(
74+
try cursor.getString(name: "id"),
75+
try cursor.getString(name: "name"),
76+
try cursor.getString(name: "email")
77+
)
78+
}
79+
XCTFail("Expected an error to be thrown")
80+
} catch {
81+
XCTAssertEqual(error.localizedDescription, """
82+
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
83+
no such table: usersfail
84+
""")
85+
}
86+
}
87+
5288
func testGetOptional() async throws {
5389
let nonExistent: String? = try await database.getOptional(
5490
sql: "SELECT name FROM users WHERE id = ?",
@@ -74,6 +110,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
74110
XCTAssertEqual(existing, "Test User")
75111
}
76112

113+
func testGetOptionalError() async throws {
114+
do {
115+
let _ = try await database.getOptional(
116+
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
117+
parameters: ["1"]
118+
) { cursor in
119+
(
120+
try cursor.getString(name: "id"),
121+
try cursor.getString(name: "name"),
122+
try cursor.getString(name: "email")
123+
)
124+
}
125+
XCTFail("Expected an error to be thrown")
126+
} catch {
127+
XCTAssertEqual(error.localizedDescription, """
128+
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
129+
no such table: usersfail
130+
""")
131+
}
132+
}
133+
77134
func testGetAll() async throws {
78135
_ = try await database.execute(
79136
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?), (?, ?, ?)",
@@ -97,6 +154,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
97154
XCTAssertEqual(users[1].1, "User 2")
98155
}
99156

157+
func testGetAllError() async throws {
158+
do {
159+
let _ = try await database.getAll(
160+
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
161+
parameters: ["1"]
162+
) { cursor in
163+
(
164+
try cursor.getString(name: "id"),
165+
try cursor.getString(name: "name"),
166+
try cursor.getString(name: "email")
167+
)
168+
}
169+
XCTFail("Expected an error to be thrown")
170+
} catch {
171+
XCTAssertEqual(error.localizedDescription, """
172+
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
173+
no such table: usersfail
174+
""")
175+
}
176+
}
177+
100178
func testWatchTableChanges() async throws {
101179
let expectation = XCTestExpectation(description: "Watch changes")
102180

@@ -119,15 +197,15 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
119197

120198
let resultsStore = ResultsStore()
121199

122-
let stream = database.watch(
200+
let stream = try database.watch(
123201
sql: "SELECT name FROM users ORDER BY id",
124202
parameters: nil
125203
) { cursor in
126204
cursor.getString(index: 0)!
127205
}
128206

129207
let watchTask = Task {
130-
for await names in stream {
208+
for try await names in stream {
131209
await resultsStore.append(names)
132210
if await resultsStore.count() == 2 {
133211
expectation.fulfill()
@@ -153,14 +231,37 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
153231
XCTAssertEqual(finalResults[1], ["User 1", "User 2"])
154232
}
155233

234+
func testWatchError() async throws {
235+
do {
236+
let stream = try database.watch(
237+
sql: "SELECT name FROM usersfail ORDER BY id",
238+
parameters: nil
239+
) { cursor in
240+
cursor.getString(index: 0)!
241+
}
242+
243+
// Actually consume the stream to trigger the error
244+
for try await _ in stream {
245+
XCTFail("Should not receive any values")
246+
}
247+
248+
XCTFail("Expected an error to be thrown")
249+
} catch {
250+
XCTAssertEqual(error.localizedDescription, """
251+
error while compiling: EXPLAIN SELECT name FROM usersfail ORDER BY id
252+
no such table: usersfail
253+
""")
254+
}
255+
}
256+
156257
func testWriteTransaction() async throws {
157258
_ = try await database.writeTransaction { transaction in
158-
_ = transaction.execute(
259+
_ = try transaction.execute(
159260
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
160261
parameters: ["1", "Test User", "test@example.com"]
161262
)
162263

163-
_ = transaction.execute(
264+
_ = try transaction.execute(
164265
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
165266
parameters: ["2", "Test User 2", "test2@example.com"]
166267
)
@@ -182,12 +283,12 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
182283

183284
_ = try await database.writeTransaction { transaction in
184285
for i in 1...loopCount {
185-
_ = transaction.execute(
286+
_ = try transaction.execute(
186287
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
187288
parameters: [String(i), "Test User \(i)", "test\(i)@example.com"]
188289
)
189290

190-
_ = transaction.execute(
291+
_ = try transaction.execute(
191292
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
192293
parameters: [String(i*10000), "Test User \(i)-2", "test\(i)-2@example.com"]
193294
)
@@ -204,6 +305,51 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
204305
XCTAssertEqual(result as! Int, 2 * loopCount)
205306
}
206307

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", "test2@example.com"]
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", "test@example.com"]
330+
)
331+
332+
_ = try transaction.execute(
333+
sql: "INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)",
334+
parameters: ["2", "Test User 2", "test2@example.com"]
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+
207353
func testReadTransaction() async throws {
208354
_ = try await database.execute(
209355
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
@@ -212,7 +358,7 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
212358

213359

214360
_ = try await database.readTransaction { transaction in
215-
let result = transaction.get(
361+
let result = try transaction.get(
216362
sql: "SELECT COUNT(*) FROM users",
217363
parameters: []
218364
) { cursor in
@@ -222,4 +368,22 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
222368
XCTAssertEqual(result as! Int, 1)
223369
}
224370
}
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+
}
225389
}

0 commit comments

Comments
 (0)
Please sign in to comment.