Skip to content

Commit aa24371

Browse files
Merge remote-tracking branch 'origin/main' into grdb
2 parents 4660475 + 44d5e88 commit aa24371

File tree

16 files changed

+402
-125
lines changed

16 files changed

+402
-125
lines changed

.github/workflows/build_and_test.yaml

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,14 @@ jobs:
1414
uses: maxim-lobanov/setup-xcode@v1
1515
with:
1616
xcode-version: latest-stable
17+
18+
- name: Test with strict concurrency
19+
run: |
20+
swift build -Xswiftc -strict-concurrency=complete
21+
swift test -Xswiftc -strict-concurrency=complete
22+
1723
- name: Build and Test
1824
run: |
1925
xcodebuild test -scheme PowerSync-Package -destination "platform=iOS Simulator,name=iPhone 16"
2026
xcodebuild test -scheme PowerSync-Package -destination "platform=macOS,arch=arm64,name=My Mac"
2127
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
33-
run: |
34-
sed -i '' 's|^// swift-tools-version:.*$|// swift-tools-version:6.1|' Package.swift
35-
- name: Build and Test
36-
run: |
37-
swift build -Xswiftc -strict-concurrency=complete
38-
swift test -Xswiftc -strict-concurrency=complete

CHANGELOG.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
11
# Changelog
22

3-
## 1.6.1 (unreleased)
3+
## 1.8.0 (unreleased)
44

5-
* Update Kotlin SDK to 1.7.0.
5+
* Enable the `newClientImplementation` by default. This should improve performance and memory usage.
6+
* **Potential Breaking Change** The `newClientImplementation` now uses WebSockets to connect to the PowerSync service. These WebSockets connections do not log events to `SyncClientConfiguration->requestLogger`.
7+
* Add the `soft` flag to `disconnectAndClear()` which keeps an internal copy of synced data in the database, allowing faster re-sync if a compatible token is used in the next connect() call
68
* Added Alpha `PowerSyncGRDB` product which supports sharing GRDB `DatabasePool`s with PowerSync and application logic.
9+
* Update PowerSync SQLite core to 0.4.10
10+
* Update Kotlin SDK to 1.7.0.
11+
12+
13+
## 1.7.0
14+
15+
* Update Kotlin SDK to 1.7.0.
16+
* Add `close(deleteDatabase:)` method to `PowerSyncDatabaseProtocol` for deleting SQLite database files when closing the database. This includes the main database file and all WAL mode files (.wal, .shm, .journal). Files that don't exist are ignored, but an error is thrown if a file exists but cannot be deleted.
17+
18+
```swift
19+
// Close the database and delete all SQLite files
20+
try await database.close(deleteDatabase: true)
21+
22+
// Close the database without deleting files (default behavior)
23+
try await database.close(deleteDatabase: false)
24+
// or simply
25+
try await database.close()
26+
```
27+
* Add `PowerSyncDataTypeConvertible` protocol for casting query parameters to SQLite supported types.
28+
* [Internal] Removed unnecessary `Task` creation in Attachment helper `FileManagerStorageAdapter`.
729

830
## 1.6.0
931

Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

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

Package.resolved

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

Package.swift

Lines changed: 20 additions & 15 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,7 +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"
10+
let localKotlinSdkOverride: String? = nil
1111

1212
// Set this to the absolute path of your powersync-sqlite-core checkout if you want to use a
1313
// local build of the core extension.
@@ -25,16 +25,18 @@ var kotlinTargetDependency = Target.Dependency.target(name: "PowerSyncKotlin")
2525
if let kotlinSdkPath = localKotlinSdkOverride {
2626
// We can't depend on local XCFrameworks outside of this project's root, so there's a Package.swift
2727
// in the PowerSyncKotlin project pointing towards a local build.
28-
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/PowerSyncKotlin"))
28+
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/internal/PowerSyncKotlin"))
2929

3030
kotlinTargetDependency = .product(name: "PowerSyncKotlin", package: "PowerSyncKotlin")
3131
} else {
3232
// Not using a local build, so download from releases
33-
conditionalTargets.append(.binaryTarget(
34-
name: "PowerSyncKotlin",
35-
url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.7.0/PowersyncKotlinRelease.zip",
36-
checksum: "836ac106c26a184c10373c862745d9af195737ad01505bb965f197797aa88535"
37-
))
33+
conditionalTargets.append(
34+
.binaryTarget(
35+
name: "PowerSyncKotlin",
36+
url:
37+
"https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.9.0/PowersyncKotlinRelease.zip",
38+
checksum: "6d9847391ab2bbbca1f6a7abe163f0682ddca4a559ef5a1d2567b3e62e7d9979"
39+
))
3840
}
3941

4042
var corePackageName = "powersync-sqlite-core-swift"
@@ -43,18 +45,19 @@ if let corePath = localCoreExtension {
4345
corePackageName = "powersync-sqlite-core"
4446
} else {
4547
// Not using a local build, so download from releases
46-
conditionalDependencies.append(.package(
47-
url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
48-
exact: "0.4.6"
49-
))
48+
conditionalDependencies.append(
49+
.package(
50+
url: "https://github.com/powersync-ja/powersync-sqlite-core-swift.git",
51+
exact: "0.4.10"
52+
))
5053
}
5154

5255
let package = Package(
5356
name: packageName,
5457
platforms: [
5558
.iOS(.v15),
5659
.macOS(.v12),
57-
.watchOS(.v9)
60+
.watchOS(.v9),
5861
],
5962
products: [
6063
// Products define the executables and libraries a package produces, making them visible to other packages.
@@ -78,7 +81,8 @@ let package = Package(
7881
)
7982
],
8083
dependencies: conditionalDependencies + [
81-
.package(url: "https://github.com/groue/GRDB.swift.git", from: "7.7.0")
84+
.package(url: "https://github.com/groue/GRDB.swift.git", from: "7.9.0"),
85+
.package(url: "https://github.com/powersync-ja/CSQLite.git", revision: "3.51.1")
8286
],
8387
targets: [
8488
// Targets are the basic building blocks of a package, defining a module or a test suite.
@@ -87,7 +91,8 @@ let package = Package(
8791
name: packageName,
8892
dependencies: [
8993
kotlinTargetDependency,
90-
.product(name: "PowerSyncSQLiteCore", package: corePackageName)
94+
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
95+
.product(name: "CSQLite", package: "CSQLite"),
9196
]
9297
),
9398
.target(

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<a href="https://www.powersync.com" target="_blank"><img src="https://github.com/powersync-ja/.github/assets/7372448/d2538c43-c1a0-4c47-9a76-41462dba484f"/></a>
33
</p>
44

5-
_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB or MySQL on the server-side._
5+
_[PowerSync](https://www.powersync.com) is a sync engine for building local-first apps with instantly-responsive UI/UX and simplified state transfer. Syncs between SQLite on the client-side and Postgres, MongoDB, MySQL or SQL Server on the server-side._
66

77
# PowerSync Swift
88

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: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import CSQLite
12
import Foundation
23
import PowerSyncKotlin
34

@@ -9,13 +10,18 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
910
private let kotlinDatabase: PowerSyncKotlin.PowerSyncDatabase
1011
private let encoder = JSONEncoder()
1112
let currentStatus: SyncStatus
13+
private let dbFilename: String
1214

1315
init(
1416
kotlinDatabase: PowerSyncKotlin.PowerSyncDatabase,
17+
dbFilename: String,
1518
logger: DatabaseLogger
1619
) {
1720
self.logger = logger
1821
self.kotlinDatabase = kotlinDatabase
22+
// We currently use the dbFilename to delete the database files when the database is closed
23+
// The kotlin PowerSyncdatabase.identifier currently prepends `null` to the dbFilename (for the directory).
24+
self.dbFilename = dbFilename
1925
currentStatus = KotlinSyncStatus(
2026
baseStatus: kotlinDatabase.currentStatus
2127
)
@@ -80,9 +86,10 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
8086
try await kotlinDatabase.disconnect()
8187
}
8288

83-
func disconnectAndClear(clearLocal: Bool = true) async throws {
89+
func disconnectAndClear(clearLocal: Bool, soft: Bool) async throws {
8490
try await kotlinDatabase.disconnectAndClear(
85-
clearLocal: clearLocal
91+
clearLocal: clearLocal,
92+
soft: soft
8693
)
8794
}
8895

@@ -316,6 +323,21 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
316323
try await kotlinDatabase.close()
317324
}
318325

326+
func close(deleteDatabase: Bool = false) async throws {
327+
// Close the SQLite connections
328+
try await close()
329+
330+
if deleteDatabase {
331+
try await self.deleteDatabase()
332+
}
333+
}
334+
335+
private func deleteDatabase() async throws {
336+
// We can use the supplied dbLocation when we support that in future
337+
let directory = try appleDefaultDatabaseDirectory()
338+
try deleteSQLiteFiles(dbFilename: dbFilename, in: directory)
339+
}
340+
319341
/// Tries to convert Kotlin PowerSyncExceptions to Swift Exceptions
320342
private func wrapPowerSyncException<R: Sendable>(
321343
handler: () async throws -> R)
@@ -391,15 +413,22 @@ final class KotlinPowerSyncDatabaseImpl: PowerSyncDatabaseProtocol,
391413
func openKotlinDBDefault(
392414
schema: Schema,
393415
dbFilename: String,
394-
logger: DatabaseLogger
416+
logger: DatabaseLogger,
395417
) -> PowerSyncDatabaseProtocol {
418+
let rc = sqlite3_initialize()
419+
if rc != 0 {
420+
fatalError("Call to sqlite3_initialize() failed with \(rc)")
421+
}
422+
423+
let factory = sqlite3DatabaseFactory(initialStatements: [])
396424
return KotlinPowerSyncDatabaseImpl(
397425
kotlinDatabase: PowerSyncDatabase(
398-
factory: PowerSyncKotlin.DatabaseDriverFactory(),
426+
factory: factory,
399427
schema: KotlinAdapter.Schema.toKotlin(schema),
400428
dbFilename: dbFilename,
401429
logger: logger.kLogger
402430
),
431+
dbFilename: dbFilename,
403432
logger: logger
404433
)
405434
}
@@ -417,6 +446,7 @@ func openKotlinDBWithPool(
417446
schema: KotlinAdapter.Schema.toKotlin(schema),
418447
logger: logger.kLogger
419448
),
449+
dbFilename: identifier,
420450
logger: logger
421451
)
422452
}
@@ -475,3 +505,46 @@ func wrapTransactionContext(
475505
}
476506
}
477507
}
508+
509+
/// This returns the default directory in which we store SQLite database files.
510+
func appleDefaultDatabaseDirectory() throws -> URL {
511+
let fileManager = FileManager.default
512+
513+
// Get the application support directory
514+
guard let documentsDirectory = fileManager.urls(
515+
for: .applicationSupportDirectory,
516+
in: .userDomainMask
517+
).first else {
518+
throw PowerSyncError.operationFailed(message: "Unable to find application support directory")
519+
}
520+
521+
return documentsDirectory.appendingPathComponent("databases")
522+
}
523+
524+
/// Deletes all SQLite files for a given database filename in the specified directory.
525+
/// This includes the main database file and WAL mode files (.wal, .shm, and .journal if present).
526+
/// Throws an error if a file exists but could not be deleted. Files that don't exist are ignored.
527+
func deleteSQLiteFiles(dbFilename: String, in directory: URL) throws {
528+
let fileManager = FileManager.default
529+
530+
// SQLite files to delete:
531+
// 1. Main database file: dbFilename
532+
// 2. WAL file: dbFilename-wal
533+
// 3. SHM file: dbFilename-shm
534+
// 4. Journal file: dbFilename-journal (for rollback journal mode, though WAL mode typically doesn't use it)
535+
536+
let filesToDelete = [
537+
dbFilename,
538+
"\(dbFilename)-wal",
539+
"\(dbFilename)-shm",
540+
"\(dbFilename)-journal"
541+
]
542+
543+
for filename in filesToDelete {
544+
let fileURL = directory.appendingPathComponent(filename)
545+
if fileManager.fileExists(atPath: fileURL.path) {
546+
try fileManager.removeItem(at: fileURL)
547+
}
548+
// If file doesn't exist, we ignore it and continue
549+
}
550+
}

Sources/PowerSync/Kotlin/db/KotlinConnectionContext.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ final class KotlinTransactionContext: Transaction, KotlinConnectionContextProtoc
9797
// Allows nil values to be passed to the Kotlin [Any] params
9898
func mapParameters(_ parameters: [Any?]?) -> [Any] {
9999
parameters?.map { item in
100-
item ?? NSNull()
100+
switch item {
101+
case .none: NSNull()
102+
case let item as PowerSyncDataTypeConvertible:
103+
item.psDataType?.unwrap() ?? NSNull()
104+
default: item as Any
105+
}
101106
} ?? []
102107
}

0 commit comments

Comments
 (0)