Skip to content

Commit 71ae180

Browse files
authored
Implement connection pool in Swift, remove Kotlin framework (#130)
1 parent dc4c0c3 commit 71ae180

71 files changed

Lines changed: 2400 additions & 1866 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
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.
10+
* Remove the internal `withSession` API.
11+
* Breaking (for internal `SQLiteConnectionPoolProtocol` implementers): Make callbacks generic.
12+
* Breaking (for internal `SQLiteConnectionLease` implementers): Add methods to run statements.
713

814
## 1.13.1
915

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.

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

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

Package.swift

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import PackageDescription
55

66
let packageName = "PowerSync"
77

8-
// Set this to the absolute path of your Kotlin SDK checkout if you want to use a local Kotlin
9-
// build. Also see docs/LocalBuild.md for details
10-
let localKotlinSdkOverride: String? = nil
11-
128
// Set this to the absolute path of your powersync-sqlite-core checkout if you want to use a
139
// local build of the core extension.
1410
let localCoreExtension: String? = nil
@@ -20,24 +16,6 @@ let localCoreExtension: String? = nil
2016
// towards a local framework build
2117
var conditionalDependencies: [Package.Dependency] = []
2218
var conditionalTargets: [Target] = []
23-
var kotlinTargetDependency = Target.Dependency.target(name: "PowerSyncKotlin")
24-
25-
if let kotlinSdkPath = localKotlinSdkOverride {
26-
// We can't depend on local XCFrameworks outside of this project's root, so there's a Package.swift
27-
// in the PowerSyncKotlin project pointing towards a local build.
28-
conditionalDependencies.append(.package(path: "\(kotlinSdkPath)/internal/PowerSyncKotlin"))
29-
30-
kotlinTargetDependency = .product(name: "PowerSyncKotlin", package: "PowerSyncKotlin")
31-
} else {
32-
// Not using a local build, so download from releases
33-
conditionalTargets.append(
34-
.binaryTarget(
35-
name: "PowerSyncKotlin",
36-
url:
37-
"https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.11.2/PowersyncKotlinRelease.zip",
38-
checksum: "bd4f9a4411a10a30bd67c3231d1d0d0dde42f0ec19161ccbd26d4e58b31efdfd"
39-
))
40-
}
4119

4220
var corePackageName = "powersync-sqlite-core-swift"
4321
if let corePath = localCoreExtension {
@@ -84,20 +62,28 @@ let package = Package(
8462
dependencies: conditionalDependencies + [
8563
.package(url: "https://github.com/groue/GRDB.swift.git", from: "7.9.0"),
8664
.package(url: "https://github.com/powersync-ja/CSQLite.git", exact: "3.51.2"),
87-
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.1.0")
65+
.package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.1.0"),
66+
.package(url: "https://github.com/apple/swift-collections.git", from: "1.4.0")
8867
],
8968
targets: [
9069
// Targets are the basic building blocks of a package, defining a module or a test suite.
9170
// Targets can depend on other targets in this package and products from dependencies.
9271
.target(
9372
name: packageName,
9473
dependencies: [
95-
kotlinTargetDependency,
96-
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
74+
.target(name: "PowerSyncCoreShim"),
9775
.product(name: "CSQLite", package: "CSQLite"),
98-
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms")
76+
.product(name: "AsyncAlgorithms", package: "swift-async-algorithms"),
77+
.product(name: "BasicContainers", package: "swift-collections"),
78+
.product(name: "DequeModule", package: "swift-collections")
9979
]
10080
),
81+
.target(
82+
name: "PowerSyncCoreShim",
83+
dependencies: [
84+
.product(name: "PowerSyncSQLiteCore", package: corePackageName),
85+
],
86+
),
10187
.target(
10288
name: "PowerSyncGRDB",
10389
dependencies: [

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

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
final class DatabaseGroupCollection: Sendable {
2+
private let groups: Mutex<[ActiveDatabaseGroupData]> = Mutex([])
3+
4+
fileprivate func closeGroup(identifier: String) {
5+
groups.withLock { $0.removeAll { group in group.identifier == identifier } }
6+
}
7+
8+
func referenceGroup(identifier: String, logger: LoggerProtocol) -> ActiveDatabaseGroup {
9+
groups.withLock { activeDatabases in
10+
let existingGroup = activeDatabases.first { $0.identifier == identifier }
11+
let data: ActiveDatabaseGroupData
12+
if let existingGroup {
13+
logger.warning("""
14+
Multiple PowerSync instances for the same database have been detected.
15+
This can cause unexpected results.
16+
Please check your PowerSync client instantiation logic if this is not intentional.
17+
""", tag: "DatabaseGroupCollection")
18+
data = existingGroup
19+
} else {
20+
data = ActiveDatabaseGroupData(identifier: identifier)
21+
activeDatabases.append(data)
22+
}
23+
24+
return ActiveDatabaseGroup(data: data, collection: self)
25+
}
26+
}
27+
28+
static let shared = DatabaseGroupCollection()
29+
}
30+
31+
private final class ActiveDatabaseGroupData: Sendable {
32+
let identifier: String
33+
let syncCoordinator = SyncCoordinator()
34+
35+
init(identifier: String) {
36+
self.identifier = identifier
37+
}
38+
}
39+
40+
/// A collection of PowerSync databases with the same path / identifier.
41+
///
42+
/// We expect that each group will only ever have one database because we encourage users to write their databases as
43+
/// singletons. We print a warning when two databases are part of the same group.
44+
/// Additionally, we want to avoid two databases in the same group having a sync stream open at the same time to avoid
45+
/// duplicate resources being used. For this reason, each active database group has a single sync coordinator actor
46+
/// responsible for initializing the sync process for all databases in the group.
47+
final class ActiveDatabaseGroup: Sendable {
48+
fileprivate let data: ActiveDatabaseGroupData
49+
private weak let collection: DatabaseGroupCollection?
50+
51+
fileprivate init(data: ActiveDatabaseGroupData, collection: DatabaseGroupCollection) {
52+
self.data = data
53+
self.collection = collection
54+
}
55+
56+
var syncCoordinator: SyncCoordinator {
57+
data.syncCoordinator
58+
}
59+
60+
deinit {
61+
if let collection {
62+
collection.closeGroup(identifier: self.data.identifier)
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)