Skip to content

Commit 28ae63f

Browse files
authored
Implement CRUD transactions in Swift instead of Kotlin (#127)
1 parent 1778b8f commit 28ae63f

13 files changed

Lines changed: 348 additions & 210 deletions

CHANGELOG.md

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

3+
## 1.14.0 (unreleased)
4+
5+
* Add `opDataTyped` and `previousValuesTyped` to `CrudEntry`, providing typed values instead of strings.
6+
* Make `CrudBatch`, `CrudEntry` and `CrudTransaction` a concrete struct. Note that these can no longer be created in user code.
7+
38
## 1.13.1
49

510
* Don't attempt to create WebSocket connections on watchOS.

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,6 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
7272
)
7373
}
7474

75-
func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {
76-
guard let base = try await kotlinDatabase.getCrudBatch(limit: limit) else {
77-
return nil
78-
}
79-
return try KotlinCrudBatch(
80-
batch: base
81-
)
82-
}
83-
84-
func getCrudTransactions() -> any CrudTransactions {
85-
return KotlinCrudTransactions(db: kotlinDatabase)
86-
}
87-
8875
func getPowerSyncVersion() async throws -> String {
8976
try await kotlinDatabase.getPowerSyncVersion()
9077
}

Sources/PowerSync/Kotlin/db/KotlinCrudBatch.swift

Lines changed: 0 additions & 27 deletions
This file was deleted.

Sources/PowerSync/Kotlin/db/KotlinCrudEntry.swift

Lines changed: 0 additions & 44 deletions
This file was deleted.

Sources/PowerSync/Kotlin/db/KotlinCrudTransaction.swift

Lines changed: 0 additions & 22 deletions
This file was deleted.

Sources/PowerSync/Kotlin/db/KotlinCrudTransactions.swift

Lines changed: 0 additions & 39 deletions
This file was deleted.

Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -191,40 +191,6 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable {
191191
options: ConnectOptions?
192192
) async throws
193193

194-
/// Get a batch of crud data to upload.
195-
///
196-
/// Returns nil if there is no data to upload.
197-
///
198-
/// Use this from the `PowerSyncBackendConnector.uploadData` callback.
199-
///
200-
/// Once the data have been successfully uploaded, call `CrudBatch.complete` before
201-
/// requesting the next batch.
202-
///
203-
/// - Parameter limit: Maximum number of updates to return in a single batch. Default is 100.
204-
///
205-
/// This method does include transaction ids in the result, but does not group
206-
/// data by transaction. One batch may contain data from multiple transactions,
207-
/// and a single transaction may be split over multiple batches.
208-
func getCrudBatch(limit: Int32) async throws -> CrudBatch?
209-
210-
/// Obtains an async iterator of completed transactions with local writes against the database.
211-
///
212-
/// This is typically used from the ``PowerSyncBackendConnectorProtocol/uploadData(database:)`` callback.
213-
/// Each entry emitted by teh returned flow is a full transaction containing all local writes made while that transaction was
214-
/// active.
215-
///
216-
/// Unlike ``getNextCrudTransaction()``, which always returns the oldest transaction that hasn't been
217-
/// ``CrudTransaction/complete()``d yet, this iterator can be used to upload multiple transactions.
218-
/// Calling ``CrudTransaction/complete()`` will mark that and all prior transactions returned by this iterator as
219-
/// completed.
220-
///
221-
/// This can be used to upload multiple transactions in a single batch, e.g. with
222-
///
223-
/// ```Swift
224-
///
225-
/// ```
226-
func getCrudTransactions() -> any CrudTransactions
227-
228194
/// Convenience method to get the current version of PowerSync.
229195
func getPowerSyncVersion() async throws -> String
230196

@@ -343,9 +309,60 @@ public extension PowerSyncDatabaseProtocol {
343309
try await disconnectAndClear(clearLocal: true, soft: soft)
344310
}
345311

312+
/// Get a batch of crud data to upload.
313+
///
314+
/// Returns nil if there is no data to upload.
315+
///
316+
/// Use this from the `PowerSyncBackendConnector.uploadData` callback.
317+
///
318+
/// Once the data have been successfully uploaded, call `CrudBatch.complete` before
319+
/// requesting the next batch.
320+
///
321+
/// - Parameter limit: Maximum number of updates to return in a single batch. Default is 100.
322+
///
323+
/// This method does include transaction ids in the result, but does not group
324+
/// data by transaction. One batch may contain data from multiple transactions,
325+
/// and a single transaction may be split over multiple batches.
346326
func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {
347-
try await getCrudBatch(
348-
limit: limit
327+
var entries = try await getAll(
328+
sql: "SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC LIMIT ?",
329+
parameters: [Int64(limit + 1)],
330+
mapper: CrudEntry.fromCursor
349331
)
332+
333+
if entries.isEmpty {
334+
return nil
335+
}
336+
337+
let hasMore = entries.count > limit
338+
if hasMore {
339+
entries.removeLast()
340+
}
341+
342+
return CrudBatch(
343+
hasMore: hasMore,
344+
crud: entries,
345+
db: self
346+
)
347+
}
348+
349+
/// Obtains an async iterator of completed transactions with local writes against the database.
350+
///
351+
/// This is typically used from the ``PowerSyncBackendConnectorProtocol/uploadData(database:)`` callback.
352+
/// Each entry emitted by teh returned flow is a full transaction containing all local writes made while that transaction was
353+
/// active.
354+
///
355+
/// Unlike ``getNextCrudTransaction()``, which always returns the oldest transaction that hasn't been
356+
/// ``CrudTransaction/complete()``d yet, this iterator can be used to upload multiple transactions.
357+
/// Calling ``CrudTransaction/complete()`` will mark that and all prior transactions returned by this iterator as
358+
/// completed.
359+
///
360+
/// This can be used to upload multiple transactions in a single batch, e.g. with
361+
///
362+
/// ```Swift
363+
///
364+
/// ```
365+
func getCrudTransactions() -> CrudTransactions {
366+
CrudTransactions(db: self)
350367
}
351368
}
Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,40 @@
11
import Foundation
22

3-
/// A transaction of client-side changes.
4-
public protocol CrudBatch: Sendable {
3+
/// A collection of client-side changes.
4+
public struct CrudBatch: Sendable {
55
/// Indicates if there are additional Crud items in the queue which are not included in this batch
6-
var hasMore: Bool { get }
6+
public let hasMore: Bool
77

88
/// List of client-side changes.
9-
var crud: [any CrudEntry] { get }
9+
public let crud: [CrudEntry]
10+
11+
private let db: PowerSyncDatabaseProtocol
12+
13+
internal init(hasMore: Bool, crud: [CrudEntry], db: PowerSyncDatabaseProtocol) {
14+
self.hasMore = hasMore
15+
self.crud = crud
16+
self.db = db
17+
}
1018

1119
/// Call to remove the changes from the local queue, once successfully uploaded.
1220
///
1321
/// `writeCheckpoint` is optional.
14-
func complete(writeCheckpoint: String?) async throws
22+
public func complete(writeCheckpoint: String? = nil) async throws {
23+
let lastId = crud.last!.clientId
24+
try await completeCrudItems(self.db, lastId, writeCheckpoint: writeCheckpoint)
25+
}
1526
}
1627

17-
public extension CrudBatch {
18-
/// Call to remove the changes from the local queue, once successfully uploaded.
19-
func complete() async throws {
20-
try await self.complete(
21-
writeCheckpoint: nil
22-
)
28+
internal func completeCrudItems(_ db: any PowerSyncDatabaseProtocol, _ lastItemId: Int64, writeCheckpoint: String? = nil) async throws {
29+
return try await db.writeTransaction { tx in
30+
try tx.execute(sql: "DELETE FROM ps_crud WHERE id <= ?", parameters: [lastItemId])
31+
if writeCheckpoint != nil {
32+
let hasCrud = (try tx.getOptional(sql: "SELECT 1 FROM ps_crud", parameters: nil) { cursor in () }) != nil
33+
if !hasCrud {
34+
try tx.execute(sql: "UPDATE ps_buckets SET target_op = CAST(? AS INTEGER) WHERE name = '$local'", parameters: [writeCheckpoint])
35+
return
36+
}
37+
}
38+
try tx.execute(sql: "UPDATE ps_buckets SET target_op = 9223372036854775807 WHERE name = '$local'", parameters: nil)
2339
}
2440
}

0 commit comments

Comments
 (0)