Skip to content

Commit

Permalink
chore: make execute and watch throwable in swift
Browse files Browse the repository at this point in the history
  • Loading branch information
DominicGBauer committed Feb 11, 2025
1 parent 72dd7e1 commit b8fe242
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 32 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 1.0.0-Beta.6

* Allow `execute` to be handled
* BREAKING CHANGE: `watch` queries are now throwable and therefore will need to be accompanied by a `try` e.g.

```swift
try database.watch()
```

## 1.0.0-Beta.5

* Implement improvements to errors originating in Kotlin so that they can be handled in Swift
Expand Down
58 changes: 33 additions & 25 deletions Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
mapper: mapper
) as! RowType
}

func get<RowType>(
sql: String,
parameters: [Any]?,
Expand All @@ -93,7 +93,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
}
) as! RowType
}

func getAll<RowType>(
sql: String,
parameters: [Any]?,
Expand All @@ -105,7 +105,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
mapper: mapper
) as! [RowType]
}

func getAll<RowType>(
sql: String,
parameters: [Any]?,
Expand All @@ -131,7 +131,7 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
mapper: mapper
) as! RowType?
}

func getOptional<RowType>(
sql: String,
parameters: [Any]?,
Expand All @@ -150,42 +150,50 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol {
sql: String,
parameters: [Any]?,
mapper: @escaping (SqlCursor) -> RowType
) -> AsyncStream<[RowType]> {
AsyncStream { continuation in
) throws -> AsyncThrowingStream<[RowType], Error> {
AsyncThrowingStream { continuation in
Task {
for await values in try self.kotlinDatabase.watch(
sql: sql,
parameters: parameters,
mapper: mapper
) {
continuation.yield(values as! [RowType])
do {
for await values in try self.kotlinDatabase.watch(
sql: sql,
parameters: parameters,
mapper: mapper
) {
continuation.yield(values as! [RowType])
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
continuation.finish()
}
}
}

func watch<RowType>(
sql: String,
parameters: [Any]?,
mapper: @escaping (SqlCursor) throws -> RowType
) -> AsyncStream<[RowType]> {
AsyncStream { continuation in
) throws -> AsyncThrowingStream<[RowType], Error> {
AsyncThrowingStream { continuation in
Task {
for await values in try self.kotlinDatabase.watch(
sql: sql,
parameters: parameters,
mapper: { cursor in
try! mapper(cursor)
do {
for await values in try self.kotlinDatabase.watch(
sql: sql,
parameters: parameters,
mapper: { cursor in
try! mapper(cursor)
}
) {
continuation.yield(values as! [RowType])
}
) {
continuation.yield(values as! [RowType])
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
continuation.finish()
}
}
}

public func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R {
return try await kotlinDatabase.writeTransaction(callback: callback) as! R
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/PowerSync/QueriesProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ public protocol Queries {
sql: String,
parameters: [Any]?,
mapper: @escaping (SqlCursor) -> RowType
) -> AsyncStream<[RowType]>
) throws -> AsyncThrowingStream<[RowType], Error>

/// Execute a read-only (SELECT) query every time the source tables are modified
/// and return the results as an array in a Publisher.
func watch<RowType>(
sql: String,
parameters: [Any]?,
mapper: @escaping (SqlCursor) throws -> RowType
) -> AsyncStream<[RowType]>
) throws -> AsyncThrowingStream<[RowType], Error>

/// Execute a write transaction with the given callback
func writeTransaction<R>(callback: @escaping (any PowerSyncTransaction) -> R) async throws -> R
Expand Down Expand Up @@ -105,7 +105,7 @@ extension Queries {
public func watch<RowType>(
_ sql: String,
mapper: @escaping (SqlCursor) -> RowType
) -> AsyncStream<[RowType]> {
return watch(sql: sql, parameters: [], mapper: mapper)
) throws -> AsyncThrowingStream<[RowType], Error> {
return try watch(sql: sql, parameters: [], mapper: mapper)
}
}
107 changes: 104 additions & 3 deletions Tests/PowerSyncTests/Kotlin/KotlinPowerSyncDatabaseImplTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,22 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
database = nil
try await super.tearDown()
}


func testExecuteError() async throws {
do {
_ = try await database.execute(
sql: "INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)",
parameters: ["1", "Test User", "[email protected]"]
)
XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error.localizedDescription, """
error while compiling: INSERT INTO usersfail (id, name, email) VALUES (?, ?, ?)
no such table: usersfail
""")
}
}

func testInsertAndGet() async throws {
_ = try await database.execute(
sql: "INSERT INTO users (id, name, email) VALUES (?, ?, ?)",
Expand All @@ -48,6 +63,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
XCTAssertEqual(user.1, "Test User")
XCTAssertEqual(user.2, "[email protected]")
}

func testGetError() async throws {
do {
let _ = try await database.get(
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
parameters: ["1"]
) { cursor in
(
try cursor.getString(name: "id"),
try cursor.getString(name: "name"),
try cursor.getString(name: "email")
)
}
XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error.localizedDescription, """
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
no such table: usersfail
""")
}
}

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

XCTAssertEqual(existing, "Test User")
}

func testGetOptionalError() async throws {
do {
let _ = try await database.getOptional(
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
parameters: ["1"]
) { cursor in
(
try cursor.getString(name: "id"),
try cursor.getString(name: "name"),
try cursor.getString(name: "email")
)
}
XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error.localizedDescription, """
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
no such table: usersfail
""")
}
}

func testGetAll() async throws {
_ = try await database.execute(
Expand All @@ -96,6 +153,27 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
XCTAssertEqual(users[1].0, "2")
XCTAssertEqual(users[1].1, "User 2")
}

func testGetAllError() async throws {
do {
let _ = try await database.getAll(
sql: "SELECT id, name, email FROM usersfail WHERE id = ?",
parameters: ["1"]
) { cursor in
(
try cursor.getString(name: "id"),
try cursor.getString(name: "name"),
try cursor.getString(name: "email")
)
}
XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error.localizedDescription, """
error while compiling: SELECT id, name, email FROM usersfail WHERE id = ?
no such table: usersfail
""")
}
}

func testWatchTableChanges() async throws {
let expectation = XCTestExpectation(description: "Watch changes")
Expand All @@ -119,15 +197,15 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {

let resultsStore = ResultsStore()

let stream = database.watch(
let stream = try database.watch(
sql: "SELECT name FROM users ORDER BY id",
parameters: nil
) { cursor in
cursor.getString(index: 0)!
}

let watchTask = Task {
for await names in stream {
for try await names in stream {
await resultsStore.append(names)
if await resultsStore.count() == 2 {
expectation.fulfill()
Expand All @@ -152,6 +230,29 @@ final class KotlinPowerSyncDatabaseImplTests: XCTestCase {
XCTAssertEqual(finalResults.count, 2)
XCTAssertEqual(finalResults[1], ["User 1", "User 2"])
}

func testWatchError() async throws {
do {
let stream = try database.watch(
sql: "SELECT name FROM usersfail ORDER BY id",
parameters: nil
) { cursor in
cursor.getString(index: 0)!
}

// Actually consume the stream to trigger the error
for try await _ in stream {
XCTFail("Should not receive any values")
}

XCTFail("Expected an error to be thrown")
} catch {
XCTAssertEqual(error.localizedDescription, """
error while compiling: EXPLAIN SELECT name FROM usersfail ORDER BY id
no such table: usersfail
""")
}
}

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

0 comments on commit b8fe242

Please sign in to comment.