Skip to content

Commit b8fe242

Browse files
committed
chore: make execute and watch throwable in swift
1 parent 72dd7e1 commit b8fe242

File tree

4 files changed

+150
-32
lines changed

4 files changed

+150
-32
lines changed

CHANGELOG.md

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

3+
## 1.0.0-Beta.6
4+
5+
* Allow `execute` to be handled
6+
* BREAKING CHANGE: `watch` queries are now throwable and therefore will need to be accompanied by a `try` e.g.
7+
8+
```swift
9+
try database.watch()
10+
```
11+
312
## 1.0.0-Beta.5
413

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

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 33 additions & 25 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,42 +150,50 @@ 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-
196+
189197
public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {
190198
return try await kotlinDatabase.writeTransaction(callback: callback) as! R
191199
}

Sources/PowerSync/QueriesProtocol.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ 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
7373
func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R
@@ -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: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,22 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
2626
database = nil
2727
try await super.tearDown()
2828
}
29-
29+
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", "[email protected]"]
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 (?, ?, ?)",
@@ -48,6 +63,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
4863
XCTAssertEqual(user.1, "Test User")
4964
XCTAssertEqual(user.2, "[email protected]")
5065
}
66+
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+
}
5187

5288
func testGetOptional() async throws {
5389
let nonExistent: String? = try await database.getOptional(
@@ -73,6 +109,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
73109

74110
XCTAssertEqual(existing, "Test User")
75111
}
112+
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+
}
76133

77134
func testGetAll() async throws {
78135
_ = try await database.execute(
@@ -96,6 +153,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
96153
XCTAssertEqual(users[1].0, "2")
97154
XCTAssertEqual(users[1].1, "User 2")
98155
}
156+
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+
}
99177

100178
func testWatchTableChanges() async throws {
101179
let expectation = XCTestExpectation(description: "Watch changes")
@@ -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()
@@ -152,6 +230,29 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
152230
XCTAssertEqual(finalResults.count, 2)
153231
XCTAssertEqual(finalResults[1], ["User 1", "User 2"])
154232
}
233+
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+
}
155256

156257
func testWriteTransaction() async throws {
157258
_ = try await database.writeTransaction { transaction in

0 commit comments

Comments
 (0)