diff --git a/CHANGELOG.md b/CHANGELOG.md index b93d1a4..ac6fe39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ # Changelog -## 1.2.2 (unreleased) +## 1.3.0 * Use version `0.4.2` of the PowerSync core extension, which improves the reliability of the new Rust client implementation. +* Add support for [raw tables](https://docs.powersync.com/usage/use-case-examples/raw-tables), which + are custom tables managed by the user instead of JSON-based views managed by the SDK. * Fix attachments never downloading again when the sandbox path of the app (e.g. on the simulator) changes. diff --git a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 76b01f7..4dfb9de 100644 --- a/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Demo/PowerSyncExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,8 +15,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/powersync-ja/powersync-sqlite-core-swift.git", "state" : { - "revision" : "06ae76a152e1868b04630f6810d9fe8a296cad35", - "version" : "0.4.1" + "revision" : "21057135ce8269b43582022aa4ca56407332e6a8", + "version" : "0.4.2" } }, { diff --git a/Package.swift b/Package.swift index 7c8cb42..74410da 100644 --- a/Package.swift +++ b/Package.swift @@ -31,8 +31,8 @@ if let kotlinSdkPath = localKotlinSdkOverride { // 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.2.2/PowersyncKotlinRelease.zip", - checksum: "51ddc7d48fa85e881c33a26ca63e4aae694ae16789368f9fbba20a467279fb97" + url: "https://github.com/powersync-ja/powersync-kotlin/releases/download/v1.3.0/PowersyncKotlinRelease.zip", + checksum: "5351c0a89e74ceaad570cd5c016c8ea4b8e10dc404daff3c20485ae0b6b989fa" )) } diff --git a/Sources/PowerSync/Kotlin/KotlinAdapter.swift b/Sources/PowerSync/Kotlin/KotlinAdapter.swift index 5ee9587..8e85220 100644 --- a/Sources/PowerSync/Kotlin/KotlinAdapter.swift +++ b/Sources/PowerSync/Kotlin/KotlinAdapter.swift @@ -44,6 +44,30 @@ enum KotlinAdapter { ignoreEmptyUpdates: table.ignoreEmptyUpdates ) } + + static func toKotlin(_ table: RawTable) -> PowerSyncKotlin.RawTable { + return PowerSyncKotlin.RawTable( + name: table.name, + put: translateStatement(table.put), + delete: translateStatement(table.delete) + ); + } + + private static func translateStatement(_ stmt: PendingStatement) -> PowerSyncKotlin.PendingStatement { + return PowerSyncKotlin.PendingStatement( + sql: stmt.sql, + parameters: stmt.parameters.map(translateParameter) + ) + } + + private static func translateParameter(_ param: PendingStatementParameter) -> PowerSyncKotlin.PendingStatementParameter { + switch param { + case .id: + return PowerSyncKotlin.PendingStatementParameterId.shared + case .column(let name): + return PowerSyncKotlin.PendingStatementParameterColumn(name: name) + } + } } struct Column { @@ -68,8 +92,12 @@ enum KotlinAdapter { struct Schema { static func toKotlin(_ schema: SchemaProtocol) -> PowerSyncKotlin.Schema { - PowerSyncKotlin.Schema( - tables: schema.tables.map { Table.toKotlin($0) } + var mappedTables: [PowerSyncKotlin.BaseTable] = [] + mappedTables.append(contentsOf: schema.tables.map(Table.toKotlin)) + mappedTables.append(contentsOf: schema.rawTables.map(Table.toKotlin)) + + return PowerSyncKotlin.Schema( + tables: mappedTables ) } } diff --git a/Sources/PowerSync/Protocol/Schema/RawTable.swift b/Sources/PowerSync/Protocol/Schema/RawTable.swift new file mode 100644 index 0000000..b92e7b8 --- /dev/null +++ b/Sources/PowerSync/Protocol/Schema/RawTable.swift @@ -0,0 +1,60 @@ +/// A table that is managed by the user instead of being auto-created and migrated by the PowerSync SDK. +/// +/// These tables give application developers full control over the table (including table and column constraints). +/// The ``RawTable/put`` and ``RawTable/delete`` statements used by the sync client to apply +/// operations to the local database also need to be set explicitly. +/// +/// A main benefit of raw tables is that, since they're not backed by JSON views, complex queries on them +/// can be much more efficient. +/// However, it's the responsibility of the developer to create these raw tables, migrate them when necessary +/// and to write triggers detecting local writes. For more information, see [the documentation](https://docs.powersync.com/usage/use-case-examples/raw-tables). +/// +/// Note that raw tables are only supported when ``ConnectOptions/newClientImplementation`` +/// is enabled. +public struct RawTable: BaseTableProtocol { + /// The name of the table as it appears in sync rules. + /// + /// This doesn't necessarily have to match the statement that ``RawTable/put`` and ``RawTable/delete`` + /// write into. + /// Instead, it is used by the sync client to identify which operations need to use which raw table definition. + public let name: String + + /// The statement to run when the sync client has to insert or update a row. + public let put: PendingStatement + + /// The statement to run when the sync client has to delete a row. + public let delete: PendingStatement + + public init(name: String, put: PendingStatement, delete: PendingStatement) { + self.name = name; + self.put = put; + self.delete = delete; + } +} + +/// A statement to run to sync server-side changes into a local raw table. +public struct PendingStatement { + /// The SQL statement to execute. + public let sql: String + /// For parameters in the prepared statement, the values to fill in. + /// + /// Note that the ``RawTable/delete`` statement can only use ``PendingStatementParameter/id`` - upsert + /// statements can also use ``PendingStatementParameter/column`` to refer to columns. + public let parameters: [PendingStatementParameter] + + public init(sql: String, parameters: [PendingStatementParameter]) { + self.sql = sql + self.parameters = parameters + } +} + +/// A parameter that can be used in a ``PendingStatement``. +public enum PendingStatementParameter { + /// A value that resolves to the textual id of the row to insert, update or delete. + case id + /// A value that resolves to the value of a column in a `PUT` operation for inserts or updates. + /// + /// Note that using this parameter is not allowed for ``RawTable/delete`` statements, which only have access + /// to the row's ``PendingStatementParameter/id``. + case column(String) +} diff --git a/Sources/PowerSync/Protocol/Schema/Schema.swift b/Sources/PowerSync/Protocol/Schema/Schema.swift index 8ef15e1..473f7db 100644 --- a/Sources/PowerSync/Protocol/Schema/Schema.swift +++ b/Sources/PowerSync/Protocol/Schema/Schema.swift @@ -3,6 +3,9 @@ public protocol SchemaProtocol { /// Tables used in Schema /// var tables: [Table] { get } + + /// Raw tables referenced in the schema. + var rawTables: [RawTable] { get } /// /// Validate tables /// @@ -11,15 +14,30 @@ public protocol SchemaProtocol { public struct Schema: SchemaProtocol { public let tables: [Table] + public let rawTables: [RawTable] - public init(tables: [Table]) { + public init(tables: [Table], rawTables: [RawTable] = []) { self.tables = tables + self.rawTables = rawTables } /// /// Convenience initializer with variadic parameters /// - public init(_ tables: Table...) { - self.init(tables: tables) + public init(_ tables: BaseTableProtocol...) { + var managedTables: [Table] = [] + var rawTables: [RawTable] = [] + + for table in tables { + if let table = table as? Table { + managedTables.append(table) + } else if let rawTable = table as? RawTable { + rawTables.append(rawTable) + } else { + fatalError("BaseTableProtocol must only be implemented in Swift SDK") + } + } + + self.init(tables: managedTables, rawTables: rawTables) } public func validate() throws { diff --git a/Sources/PowerSync/Protocol/Schema/Table.swift b/Sources/PowerSync/Protocol/Schema/Table.swift index 0bf548e..73f983a 100644 --- a/Sources/PowerSync/Protocol/Schema/Table.swift +++ b/Sources/PowerSync/Protocol/Schema/Table.swift @@ -1,10 +1,15 @@ import Foundation -public protocol TableProtocol { +/// Shared protocol for both PowerSync-managed ``Table``s as well as ``RawTable``s managed by the user. +public protocol BaseTableProtocol { /// /// The synced table name, matching sync rules. /// var name: String { get } +} + +/// Protocol for describing ``Table``s managed by PowerSync. +public protocol TableProtocol: BaseTableProtocol { /// /// List of columns. ///