Skip to content

Commit 937ed8c

Browse files
committed
Fix GRDB tests
1 parent 2321496 commit 937ed8c

16 files changed

Lines changed: 167 additions & 211 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22

33
## 1.14.0 (unreleased)
44

5+
* Remove internal dependency on the PowerSync Kotlin SDK. Going forward, the Swift SDK is implemented in Swift!
6+
__Important__: While these changes are tested, they are a full rewrite of the internal connection pool logic.
7+
Please also test queries in your app after upgrading.
58
* Add `opDataTyped` and `previousValuesTyped` to `CrudEntry`, providing typed values instead of strings.
69
* Make `CrudBatch`, `CrudEntry` and `CrudTransaction` a concrete struct. Note that these can no longer be created in user code.
710
* Remove the internal `withSession` API.
8-
* Remove internal dependency on the PowerSync Kotlin SDK. Going forward, the Swift SDK is implemented in Swift!
911
* Breaking (for internal `SQLiteConnectionPoolProtocol` implementers): Make callbacks generic.
1012
* Breaking (for internal `SQLiteConnectionLease` implementers): Add methods to run statements.
1113

Demos/GRDBDemo/GRDBDemo.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@
314314
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
315315
CODE_SIGN_STYLE = Automatic;
316316
CURRENT_PROJECT_VERSION = 1;
317-
DEVELOPMENT_TEAM = ZGT7463CVJ;
317+
DEVELOPMENT_TEAM = N2FNDTNV98;
318318
ENABLE_APP_SANDBOX = YES;
319319
ENABLE_HARDENED_RUNTIME = YES;
320320
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;
@@ -368,7 +368,7 @@
368368
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
369369
CODE_SIGN_STYLE = Automatic;
370370
CURRENT_PROJECT_VERSION = 1;
371-
DEVELOPMENT_TEAM = ZGT7463CVJ;
371+
DEVELOPMENT_TEAM = N2FNDTNV98;
372372
ENABLE_APP_SANDBOX = YES;
373373
ENABLE_HARDENED_RUNTIME = YES;
374374
ENABLE_INCOMING_NETWORK_CONNECTIONS = NO;

Demos/GRDBDemo/GRDBDemo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 23 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,6 @@ Feel free to use the `DatabasePool` for view logic and the `PowerSyncDatabase` f
118118
- Updating the PowerSync schema, with `updateSchema`, is not currently fully supported with GRDB connections.
119119
- This integration currently requires statically linking PowerSync and GRDB.
120120
- This implementation requires defining the PowerSync Schema and GRDB data types. This results in some duplication.
121-
- This implementation uses the SQLite session API to track updates made by the PowerSync SDK. This could use more memory compared to the Standard PowerSync SQLite implementation.
122-
- This implementation can cause warnings such as "Thread Performance Checker: Thread running at User-interactive quality-of-service class waiting on a lower QoS thread running at Default quality-of-service class. Investigate ways to avoid priority inversions". This will be addressed in a future release.
123-
124-
## Underlying Kotlin Dependency
125-
126-
The PowerSync Swift SDK makes use of the [PowerSync Kotlin SDK](https://github.com/powersync-ja/powersync-kotlin) and the API tool [SKIE](https://skie.touchlab.co/) under the hood to implement the Swift package.
127-
However, this dependency is resolved internally and all public APIs are written entirely in Swift.
128-
129-
For more details, see the [Swift SDK reference](https://docs.powersync.com/client-sdk-references/swift) and generated [API references](https://powersync-ja.github.io/powersync-swift/documentation/powersync/).
130121

131122
## Attachments
132123

Sources/PowerSync/Implementation/AsyncConnectionPool.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ final class AsyncConnectionPool: SQLiteConnectionPoolProtocol {
102102
if isWriter {
103103
// Older versions of the SDK used to set up an empty schema and raise the user version to 1.
104104
// Keep doing that for consistency.
105-
let version = try context.withIterator(sql: "pragma user_version", parameters: []) { rows in
106-
try rows.next { try $0.getInt(index: 0) }
107-
}
105+
var stmt = try context.iterate(sql: "pragma user_version", parameters: [])
106+
let version = try stmt.stepWithCursor { try $0.getInt(index: 0) }
107+
let _ = consume stmt
108108
if let version, version < 1 {
109109
let _ = try context.execute(sql: "pragma user_version = 1", parameters: [])
110110
}

Sources/PowerSync/Implementation/queries/LeaseContext.swift

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,31 @@ final class ConnectionLeaseContext: ConnectionContext {
3131

3232
func getOptional<RowType>(sql: String, parameters: [(any Sendable)?]?, mapper: @escaping @Sendable (any SqlCursor) throws -> RowType) throws -> RowType? {
3333
try lease.withLock { lease in
34-
try lease.withIterator(sql: sql, parameters: mapParameters(parameters)) { rows in
35-
return try rows.next(callback: mapper)
36-
}
34+
var stmt = try lease.iterate(sql: sql, parameters: mapParameters(parameters))
35+
return try stmt.stepWithCursor(callback: mapper)
3736
}
3837
}
3938

4039
func getAll<RowType>(sql: String, parameters: [(any Sendable)?]?, mapper: @escaping @Sendable (any SqlCursor) throws -> RowType) throws -> [RowType] {
4140
try lease.withLock { lease in
42-
try lease.withIterator(sql: sql, parameters: mapParameters(parameters)) { rows in
43-
var result: [RowType] = []
44-
while let row = try rows.next(callback: mapper) {
45-
result.append(row)
46-
}
47-
return result
41+
var stmt = try lease.iterate(sql: sql, parameters: mapParameters(parameters))
42+
var result: [RowType] = []
43+
44+
while let row = try stmt.stepWithCursor(callback: mapper) {
45+
result.append(row)
4846
}
47+
return result
4948
}
5049
}
5150

5251
func get<RowType>(sql: String, parameters: [(any Sendable)?]?, mapper: @escaping @Sendable (any SqlCursor) throws -> RowType) throws -> RowType {
5352
try lease.withLock { lease in
54-
try lease.withIterator(sql: sql, parameters: mapParameters(parameters)) { rows in
55-
guard let cursor = try rows.next(callback: mapper) else {
56-
throw PowerSyncError.operationFailed(message: "Expected \(sql) to return a row, but got an empty result set.")
57-
}
58-
return cursor
53+
var stmt = try lease.iterate(sql: sql, parameters: mapParameters(parameters))
54+
55+
if let row = try stmt.stepWithCursor(callback: mapper) {
56+
return row
57+
} else {
58+
throw PowerSyncError.operationFailed(message: "Expected \(sql) to return a row, but got an empty result set.")
5959
}
6060
}
6161
}

Sources/PowerSync/Implementation/sqlite3/NativeConnectionPool.swift

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,14 @@ final class NativeConnectionPool: Sendable {
3737

3838
private func dispatchWrites(lease: NativeConnectionLease) {
3939
do {
40-
try lease.withIterator(sql: "SELECT powersync_update_hooks('get')", parameters: []) { rows in
41-
let affectedTables = try rows.next {
42-
let decoder = JSONDecoder()
43-
return try decoder.decode(Set<String>.self, from: try $0.getString(index: 0).data(using: .utf8)!)
44-
}
40+
var stmt = try lease.iterate(sql: "SELECT powersync_update_hooks('get')", parameters: [])
41+
let affectedTables = try stmt.stepWithCursor {
42+
let decoder = JSONDecoder()
43+
return try decoder.decode(Set<String>.self, from: try $0.getString(index: 0).data(using: .utf8)!)
44+
}
4545

46-
if let affectedTables, !affectedTables.isEmpty {
47-
self.handleUpdates(affectedTables)
48-
}
46+
if let affectedTables, !affectedTables.isEmpty {
47+
self.handleUpdates(affectedTables)
4948
}
5049
} catch {
5150
logger.warning("Could not read affected tables", tag: "NativeConnectionPool")
@@ -138,39 +137,4 @@ struct RawSqliteConnection: ~Copyable {
138137
// We can't generally assume SQLite connections to be thread-safe.
139138
struct NativeConnectionLease: SQLiteConnectionLease, @unchecked Sendable {
140139
let pointer: OpaquePointer
141-
142-
func execute(sql: String, parameters: [PowerSyncDataType?]) throws -> Int64 {
143-
do {
144-
var stmt = try NativeSqliteStatement(db: pointer, sql: sql)
145-
try stmt.bindValues(parameters)
146-
while try stmt.step() {
147-
// Iterate through the statement.
148-
}
149-
}
150-
151-
return sqlite3_changes64(pointer)
152-
}
153-
154-
func withIterator<T>(sql: String, parameters: [PowerSyncDataType?], callback: (SQLiteStatementIteratorProtocol) throws -> T) throws -> T {
155-
var stmt = try NativeSqliteStatement(db: pointer, sql: sql)
156-
try stmt.bindValues(parameters)
157-
return try withUnsafeMutablePointer(to: &stmt) { ptr in
158-
let iterator = NativeStatementIterator(stmt: ptr)
159-
return try callback(iterator)
160-
}
161-
}
162-
}
163-
164-
private struct NativeStatementIterator: SQLiteStatementIteratorProtocol {
165-
var stmt: UnsafeMutablePointer<NativeSqliteStatement>
166-
167-
func next<T>(callback: (any SqlCursor) throws -> T) throws -> T? {
168-
if try stmt.pointee.step() {
169-
let cursor = StatementCursor(stmt)
170-
defer { cursor.invalidate() }
171-
return try callback(cursor)
172-
} else {
173-
return nil
174-
}
175-
}
176140
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import CSQLite
2+
3+
/// Implements functions to execute and iterate through SQL statements based on a SQLite connection pointer.
4+
extension SQLiteConnectionLease {
5+
func execute(sql: String, parameters: [PowerSyncDataType?]) throws -> Int64 {
6+
do {
7+
var stmt = try NativeSqliteStatement(db: pointer, sql: sql)
8+
try stmt.bindValues(parameters)
9+
while try stmt.step() {
10+
// Iterate through the statement.
11+
}
12+
}
13+
14+
return sqlite3_changes64(pointer)
15+
}
16+
17+
func iterate(sql: String, parameters: [PowerSyncDataType?]) throws -> NativeSqliteStatement {
18+
let stmt = try NativeSqliteStatement(db: pointer, sql: sql)
19+
try stmt.bindValues(parameters)
20+
return stmt
21+
}
22+
}

Sources/PowerSync/Implementation/sqlite3/NativeStatement.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,18 @@ struct NativeSqliteStatement: ~Copyable {
125125
try throwDatabaseError(db: self.db, sql: self.sql)
126126
}
127127
}
128+
129+
mutating func stepWithCursor<T>(callback: (any SqlCursor) throws -> T) throws -> T? {
130+
if try step() {
131+
// Turn the static ~Copyable lifetime into a dynamic lifetime with explicit
132+
// invalidation.
133+
return try withUnsafePointer(to: self) { ptr in
134+
let cursor = StatementCursor(ptr)
135+
defer { cursor.invalidate() }
136+
return try callback(cursor)
137+
}
138+
} else {
139+
return nil
140+
}
141+
}
128142
}

Sources/PowerSync/Protocol/SQLiteConnectionPool.swift

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@ public protocol SQLiteConnectionLease {
55
/// Pointer to the underlying SQLite connection.
66
/// This pointer should not be used outside of the closure which provided the lease.
77
var pointer: OpaquePointer { borrowing get }
8-
9-
/// Executes an SQL statement and returns the amount of rows affected.
10-
func execute(sql: String, parameters: [PowerSyncDataType?]) throws -> Int64
11-
12-
func withIterator<T>(sql: String, parameters: [PowerSyncDataType?], callback: (_: SQLiteStatementIteratorProtocol) throws -> T) throws -> T
13-
}
14-
15-
public protocol SQLiteStatementIteratorProtocol {
16-
func next<T>(callback: (_ cursor: SqlCursor) throws -> T) throws -> T?
178
}
189

1910
/// An implementation of a connection pool providing asynchronous access to a single writer and multiple readers.

0 commit comments

Comments
 (0)