Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 10 additions & 7 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 5.7
// swift-tools-version: 6.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -25,15 +25,15 @@ var kotlinTargetDependency = Target.Dependency.target(name: "PowerSyncKotlin")
if let kotlinSdkPath = localKotlinSdkOverride {
// We can't depend on local XCFrameworks outside of this project's root, so there's a Package.swift
// in the PowerSyncKotlin project pointing towards a local build.
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/PowerSyncKotlin"))
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/internal/PowerSyncKotlin"))

kotlinTargetDependency = .product(name: "PowerSyncKotlin", package: "PowerSyncKotlin")
} else {
// Not using a local build, so download from releases
conditionalTargets.append(.binaryTarget(
name: "PowerSyncKotlin",
url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.7.0/PowersyncKotlinRelease.zip",
checksum: "836ac106c26a184c10373c862745d9af195737ad01505bb965f197797aa88535"
url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.9.0/PowersyncKotlinRelease.zip",
checksum: "6d9847391ab2bbbca1f6a7abe163f0682ddca4a559ef5a1d2567b3e62e7d9979"
))
}

Expand All @@ -45,7 +45,7 @@ if let corePath = localCoreExtension {
// Not using a local build, so download from releases
conditionalDependencies.append(.package(
url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
exact: "0.4.6"
exact: "0.4.10"
))
}

Expand Down Expand Up @@ -73,15 +73,18 @@ let package = Package(
targets: ["PowerSync"]
)
],
dependencies: conditionalDependencies,
dependencies: conditionalDependencies + [
.package(path: "/Users/simon/src/CSQLite")
],
targets: [
// Targets are the basic building blocks of a package, defining a module or a test suite.
// Targets can depend on other targets in this package and products from dependencies.
.target(
name: packageName,
dependencies: [
kotlinTargetDependency,
.product(name: "PowerSyncSQLiteCore", package: corePackageName)
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
.product(name: "CSQLite", package: "CSQLite")
]
),
.testTarget(
Expand Down
3 changes: 2 additions & 1 deletion Sources/PowerSync/Kotlin/KotlinAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ enum KotlinAdapter {
return PowerSyncKotlin.RawTable(
name: table.name,
put: translateStatement(table.put),
delete: translateStatement(table.delete)
delete: translateStatement(table.delete),
clear: table.clear
);
}

Expand Down
13 changes: 10 additions & 3 deletions Sources/PowerSync/Kotlin/KotlinPowerSyncDatabaseImpl.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import PowerSyncKotlin
import CSQLite

final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
// `PowerSyncKotlin.PowerSyncDatabase` cannot be marked as Sendable
Expand All @@ -15,7 +16,12 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
dbFilename: String,
logger: DatabaseLogger
) {
let factory = PowerSyncKotlin.DatabaseDriverFactory()
let rc = sqlite3_initialize();
if (rc != 0) {
fatalError("Call to sqlite3_initialize() failed with \(rc)")
}

let factory = sqlite3DatabaseFactory(initialStatements: [])
kotlinDatabase = PowerSyncDatabase(
factory: factory,
schema: KotlinAdapter.Schema.toKotlin(schema),
Expand Down Expand Up @@ -87,9 +93,10 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
try await kotlinDatabase.disconnect()
}

func disconnectAndClear(clearLocal: Bool = true) async throws {
func disconnectAndClear(clearLocal: Bool, soft: Bool) async throws {
try await kotlinDatabase.disconnectAndClear(
clearLocal: clearLocal
clearLocal: clearLocal,
soft: soft
)
}

Expand Down
28 changes: 22 additions & 6 deletions Sources/PowerSync/Protocol/PowerSyncDatabaseProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,20 @@ public protocol PowerSyncDatabaseProtocol: Queries, Sendable {
func disconnect() async throws

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

/// Close the database, releasing resources.
/// Also disconnects any active connection.
Expand Down Expand Up @@ -290,7 +298,15 @@ public extension PowerSyncDatabaseProtocol {
}

func disconnectAndClear() async throws {
try await disconnectAndClear(clearLocal: true)
try await disconnectAndClear(clearLocal: true, soft: false)
}

func disconnectAndClear(clearLocal: Bool) async throws {
try await disconnectAndClear(clearLocal: clearLocal, soft: false)
}

func disconnectAndClear(soft: Bool) async throws {
try await disconnectAndClear(clearLocal: true, soft: soft)
}

func getCrudBatch(limit: Int32 = 100) async throws -> CrudBatch? {
Expand Down
6 changes: 5 additions & 1 deletion Sources/PowerSync/Protocol/Schema/RawTable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ public struct RawTable: BaseTableProtocol {

/// The statement to run when the sync client has to delete a row.
public let delete: PendingStatement

/// An optional statement to run when the database is cleared.
public let clear: String?

public init(name: String, put: PendingStatement, delete: PendingStatement) {
public init(name: String, put: PendingStatement, delete: PendingStatement, clear: String? = nil) {
self.name = name
self.put = put
self.delete = delete
self.clear = clear
}
}

Expand Down
15 changes: 15 additions & 0 deletions Tests/PowerSyncTests/CrudTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,19 @@ final class CrudTests: XCTestCase {
let finalTx = try await database.getNextCrudTransaction()
XCTAssertEqual(finalTx!.crud.count, 15)
}

func testSoftClear() async throws {
try await database.execute(sql: "INSERT INTO users (id, name) VALUES (uuid(), ?)", parameters: ["test"]);
try await database.execute(sql: "INSERT INTO ps_buckets (name, last_applied_op) VALUES (?, ?)", parameters: ["bkt", 10])

// Doing a soft-clear should delete data but keep the bucket around.
try await database.disconnectAndClear(soft: true)
let entries = try await database.getAll("SELECT name FROM ps_buckets", mapper: { cursor in try cursor.getString(index: 0) })
XCTAssertEqual(entries.count, 1)

// Doing a default clear also deletes buckets.
try await database.disconnectAndClear();
let newEntries = try await database.getAll("SELECT name FROM ps_buckets", mapper: { cursor in try cursor.getString(index: 0) })
XCTAssertEqual(newEntries.count, 0)
}
}
Loading