Skip to content

Commit 241e322

Browse files
Merge remote-tracking branch 'origin/pre-update-kotlin' into grdb-csqlite
2 parents b4d9cbf + 6913958 commit 241e322

File tree

8 files changed

+104
-41
lines changed

8 files changed

+104
-41
lines changed

.github/workflows/build_and_test.yaml

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,13 @@ jobs:
1414
uses: maxim-lobanov/setup-xcode@v1
1515
with:
1616
xcode-version: latest-stable
17-
- name: Build and Test
17+
18+
- name: Test on iOS simulator
1819
run: |
1920
xcodebuild test -scheme PowerSync-Package -destination "platform=iOS Simulator,name=iPhone 16"
20-
xcodebuild test -scheme PowerSync-Package -destination "platform=macOS,arch=arm64,name=My Mac"
21-
xcodebuild test -scheme PowerSync-Package -destination "platform=watchOS Simulator,arch=arm64,name=Apple Watch Ultra 2 (49mm)"
22-
23-
buildSwift6:
24-
name: Build and test with Swift 6
25-
runs-on: macos-latest
26-
steps:
27-
- uses: actions/checkout@v4
28-
- name: Set up XCode
29-
uses: maxim-lobanov/setup-xcode@v1
30-
with:
31-
xcode-version: latest-stable
32-
- name: Use Swift 6
21+
- name: Test on macOS
3322
run: |
34-
sed -i '' 's|^// swift-tools-version:.*$|// swift-tools-version:6.1|' Package.swift
35-
- name: Build and Test
23+
xcodebuild test -scheme PowerSync-Package -destination "platform=macOS,arch=arm64,name=My Mac"
24+
- name: Test on watchOS simulator
3625
run: |
37-
swift build -Xswiftc -strict-concurrency=complete
38-
swift test -Xswiftc -strict-concurrency=complete
26+
xcodebuild test -scheme PowerSync-Package -destination "platform=watchOS Simulator,arch=arm64,name=Apple Watch Ultra 2 (49mm)"

Package.resolved

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

Package.swift

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.7
1+
// swift-tools-version: 6.1
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription
@@ -7,8 +7,7 @@ let packageName = "PowerSync"
77

88
// Set this to the absolute path of your Kotlin SDK checkout if you want to use a local Kotlin
99
// build. Also see docs/LocalBuild.md for details
10-
let localKotlinSdkOverride: String? = "/Users/stevenontong/Documents/platform_code/powersync/powersync-kotlin/internal"
11-
10+
let localKotlinSdkOverride: String? = nil
1211
// Set this to the absolute path of your powersync-sqlite-core checkout if you want to use a
1312
// local build of the core extension.
1413
let localCoreExtension: String? = nil
@@ -18,22 +17,43 @@ let localCoreExtension: String? = nil
1817
// a binary target.
1918
// With a local SDK, we point to a `Package.swift` within the Kotlin SDK containing a target pointing
2019
// towards a local framework build
21-
var conditionalDependencies: [Package.Dependency] = []
20+
var conditionalDependencies: [Package.Dependency] = [
21+
.package(
22+
url: "https://github.com/sbooth/CSQLite.git",
23+
from: "3.50.4",
24+
traits: [
25+
.defaults,
26+
// CSQLite uses THREADSAFE=0 by default, which breaks PowerSync because we're using SQLite on
27+
// multiple threads (it can lead to race conditions when closing connections sharing resources
28+
// like shared memory, causing crashes).
29+
// THREADSAFE=2 overrides the default, and is safe to use as long as a single SQLite connection
30+
// is not shared between threads.
31+
// TODO: Technically, we should not use .defaults because there's a logical conflict between
32+
// the threadsafe options. Instead, we should spell out all defaults again and remove that
33+
// thread-safety option.
34+
// However, despite the docs explicitly saying something else, it looks like there's no way to
35+
// disable default traits anyway (XCode compiles sqlite3.c with the default option even without
36+
// .defaults being included here).
37+
"THREADSAFE_2",
38+
"ENABLE_SESSION"
39+
]
40+
)
41+
]
2242
var conditionalTargets: [Target] = []
2343
var kotlinTargetDependency = Target.Dependency.target(name: "PowerSyncKotlin")
2444

2545
if let kotlinSdkPath = localKotlinSdkOverride {
2646
// We can't depend on local XCFrameworks outside of this project's root, so there's a Package.swift
2747
// in the PowerSyncKotlin project pointing towards a local build.
28-
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/PowerSyncKotlin"))
48+
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/internal/PowerSyncKotlin"))
2949

3050
kotlinTargetDependency = .product(name: "PowerSyncKotlin", package: "PowerSyncKotlin")
3151
} else {
3252
// Not using a local build, so download from releases
3353
conditionalTargets.append(.binaryTarget(
3454
name: "PowerSyncKotlin",
35-
url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.7.0/PowersyncKotlinRelease.zip",
36-
checksum: "836ac106c26a184c10373c862745d9af195737ad01505bb965f197797aa88535"
55+
url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.8.0/PowersyncKotlinRelease.zip",
56+
checksum: "31ac7c5e11d747e11bceb0b34f30438d37033e700c621b0a468aa308d887587f"
3757
))
3858
}
3959

@@ -45,7 +65,7 @@ if let corePath = localCoreExtension {
4565
// Not using a local build, so download from releases
4666
conditionalDependencies.append(.package(
4767
url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
48-
exact: "0.4.6"
68+
exact: "0.4.8"
4969
))
5070
}
5171

@@ -87,7 +107,8 @@ let package = Package(
87107
name: packageName,
88108
dependencies: [
89109
kotlinTargetDependency,
90-
.product(name: "PowerSyncSQLiteCore", package: corePackageName)
110+
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
111+
.product(name: "CSQLite", package: "CSQLite")
91112
]
92113
),
93114
.target(

Sources/PowerSync/Kotlin/KotlinAdapter.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ enum KotlinAdapter {
4949
return PowerSyncKotlin.RawTable(
5050
name: table.name,
5151
put: translateStatement(table.put),
52-
delete: translateStatement(table.delete)
52+
delete: translateStatement(table.delete),
53+
clear: table.clear,
5354
);
5455
}
5556

Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import CSQLite
12
import Foundation
23
import PowerSyncKotlin
34

@@ -80,9 +81,10 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
8081
try await kotlinDatabase.disconnect()
8182
}
8283

83-
func disconnectAndClear(clearLocal: Bool = true) async throws {
84+
func disconnectAndClear(clearLocal: Bool, soft: Bool) async throws {
8485
try await kotlinDatabase.disconnectAndClear(
85-
clearLocal: clearLocal
86+
clearLocal: clearLocal,
87+
soft: soft
8688
)
8789
}
8890

@@ -393,9 +395,15 @@ func openKotlinDBDefault(
393395
dbFilename: String,
394396
logger: DatabaseLogger
395397
) -> PowerSyncDatabaseProtocol {
398+
let rc = sqlite3_initialize()
399+
if rc != 0 {
400+
fatalError("Call to sqlite3_initialize() failed with \(rc)")
401+
}
402+
403+
let factory = sqlite3DatabaseFactory(initialStatements: [])
396404
return KotlinPowerSyncDatabaseImpl(
397405
kotlinDatabase: PowerSyncDatabase(
398-
factory: PowerSyncKotlin.DatabaseDriverFactory(),
406+
factory: factory,
399407
schema: KotlinAdapter.Schema.toKotlin(schema),
400408
dbFilename: dbFilename,
401409
logger: logger.kLogger

Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,20 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable {
216216
func disconnect() async throws
217217

218218
/// Disconnect and clear the database.
219-
/// Use this when logging out.
220-
/// The database can still be queried after this is called, but the tables
221-
/// would be empty.
222219
///
223-
/// - Parameter clearLocal: Set to false to preserve data in local-only tables. Defaults to `true`.
224-
func disconnectAndClear(clearLocal: Bool) async throws
220+
/// Clearing the database is useful when a user logs out, to ensure another user logging in later would not see
221+
/// previous data.
222+
///
223+
/// The database can still be queried after this is called, but the tables would be empty.
224+
///
225+
/// To perserve data in local-only tables, set `clearLocal` to `false`.
226+
///
227+
/// A `soft` clear deletes publicly visible data, but keeps internal copies of data synced in the database. This
228+
/// usually means that if the same user logs out and back in again, the first sync is very fast because all internal
229+
/// data is still available. When a different user logs in, no old data would be visible at any point.
230+
/// Using soft clears is recommended where it's not a security issue that old data could be reconstructed from
231+
/// the database.
232+
func disconnectAndClear(clearLocal: Bool, soft: Bool) async throws
225233

226234
/// Close the database, releasing resources.
227235
/// Also disconnects any active connection.
@@ -290,7 +298,15 @@ public extension PowerSyncDatabaseProtocol {
290298
}
291299

292300
func disconnectAndClear() async throws {
293-
try await disconnectAndClear(clearLocal: true)
301+
try await disconnectAndClear(clearLocal: true, soft: false)
302+
}
303+
304+
func disconnectAndClear(clearLocal: Bool) async throws {
305+
try await disconnectAndClear(clearLocal: clearLocal, soft: false)
306+
}
307+
308+
func disconnectAndClear(soft: Bool) async throws {
309+
try await disconnectAndClear(clearLocal: true, soft: soft)
294310
}
295311

296312
func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {

Sources/PowerSync/Protocol/Schema/RawTable.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,15 @@ public struct RawTable: BaseTableProtocol {
2424

2525
/// The statement to run when the sync client has to delete a row.
2626
public let delete: PendingStatement
27+
28+
/// An optional statement to run when the database is cleared.
29+
public let clear: String?
2730

28-
public init(name: String, put: PendingStatement, delete: PendingStatement) {
31+
public init(name: String, put: PendingStatement, delete: PendingStatement, clear: String? = nil) {
2932
self.name = name
3033
self.put = put
3134
self.delete = delete
35+
self.clear = clear
3236
}
3337
}
3438

Tests/PowerSyncTests/CrudTests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,19 @@ final class CrudTests: XCTestCase {
238238
let finalTx = try await database.getNextCrudTransaction()
239239
XCTAssertEqual(finalTx!.crud.count, 15)
240240
}
241+
242+
func testSoftClear() async throws {
243+
try await database.execute(sql: "INSERT INTO users (id, name) VALUES (uuid(), ?)", parameters: ["test"]);
244+
try await database.execute(sql: "INSERT INTO ps_buckets (name, last_applied_op) VALUES (?, ?)", parameters: ["bkt", 10])
245+
246+
// Doing a soft-clear should delete data but keep the bucket around.
247+
try await database.disconnectAndClear(soft: true)
248+
let entries = try await database.getAll("SELECT name FROM ps_buckets", mapper: { cursor in try cursor.getString(index: 0) })
249+
XCTAssertEqual(entries.count, 1)
250+
251+
// Doing a default clear also deletes buckets.
252+
try await database.disconnectAndClear();
253+
let newEntries = try await database.getAll("SELECT name FROM ps_buckets", mapper: { cursor in try cursor.getString(index: 0) })
254+
XCTAssertEqual(newEntries.count, 0)
255+
}
241256
}

0 commit comments

Comments
 (0)