diff --git a/Sources/SQLiteKit/SQLiteConfiguration.swift b/Sources/SQLiteKit/SQLiteConfiguration.swift index 66e8857..6a6d199 100644 --- a/Sources/SQLiteKit/SQLiteConfiguration.swift +++ b/Sources/SQLiteKit/SQLiteConfiguration.swift @@ -1,4 +1,6 @@ +#if canImport(Foundation) import struct Foundation.UUID +#endif /// Describes a configuration for an SQLite database connection. public struct SQLiteConfiguration: Sendable { @@ -8,7 +10,13 @@ public struct SQLiteConfiguration: Sendable { /// /// See ``memory(identifier:)``. public static var memory: Self { + #if canImport(Foundation) .memory(identifier: UUID().uuidString) + #else + // Foundation.UUID is unavailable in Embedded Swift; derive a unique identifier from + // system randomness instead. + .memory(identifier: "memory-\(UInt64.random(in: .min ... .max))") + #endif } /// Specify an SQLite database stored in memory, using a given identifier string. diff --git a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift index 19ed883..9d8964a 100644 --- a/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift +++ b/Sources/SQLiteKit/SQLiteConnection+SQLKit.swift @@ -143,6 +143,9 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { Self.components(of: self.intValue).patch } + // `as? Self` is a cast to a generic type, which Embedded Swift does not support. There, the + // `SQLDatabaseReportedVersion` protocol's default `stringValue`-based implementations apply. + #if !hasFeature(Embedded) // See `SQLDatabaseReportedVersion.isEqual(to:)`. func isEqual(to otherVersion: any SQLDatabaseReportedVersion) -> Bool { (otherVersion as? Self).map { $0.intValue == self.intValue } ?? false @@ -156,6 +159,7 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { (self.patchVersion < $0.patchVersion))) } ?? false } + #endif } /// Wraps a `SQLiteDatabase` with the `SQLDatabase` protocol. @@ -250,7 +254,12 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion { let (sql, rawBinds) = self.serialize(query) if let queryLogLevel = self.queryLogLevel { + #if hasFeature(Embedded) + // Embedded Swift has no reflection, so bound values cannot be interpolated for logging. + self.logger.log(level: queryLogLevel, "Executing query", metadata: ["sql": .string(sql)]) + #else self.logger.log(level: queryLogLevel, "Executing query", metadata: ["sql": .string(sql), "binds": .array(rawBinds.map { .string("\($0)") })]) + #endif } try await self.database.query( diff --git a/Sources/SQLiteKit/SQLiteDataDecoder.swift b/Sources/SQLiteKit/SQLiteDataDecoder.swift index be1333c..eaabc8c 100644 --- a/Sources/SQLiteKit/SQLiteDataDecoder.swift +++ b/Sources/SQLiteKit/SQLiteDataDecoder.swift @@ -1,3 +1,17 @@ +#if hasFeature(Embedded) +import SQLiteNIO + +/// Placeholder ``SQLiteDataDecoder`` for the Embedded Swift build. +/// +/// `Decodable`-based row decoding — the only thing this type does elsewhere — is unavailable in +/// Embedded Swift, where `SQLKit/SQLRow` exposes no generic `decode(column:as:)`. Typed access is +/// performed directly against the concrete `SQLiteRow` via `SQLiteDataConvertible` there. This stub +/// exists only so the ``SQLiteDataDecoder`` API surface (e.g. `sql(decoder:)`) stays uniform. +public struct SQLiteDataDecoder: Sendable { + /// Initialize a ``SQLiteDataDecoder``. + public init() {} +} +#else import Foundation import SQLiteNIO @_spi(CodableUtilities) import SQLKit @@ -115,3 +129,4 @@ public struct SQLiteDataDecoder: Sendable { } } } +#endif // hasFeature(Embedded) diff --git a/Sources/SQLiteKit/SQLiteDataEncoder.swift b/Sources/SQLiteKit/SQLiteDataEncoder.swift index 3f4299a..439eb26 100644 --- a/Sources/SQLiteKit/SQLiteDataEncoder.swift +++ b/Sources/SQLiteKit/SQLiteDataEncoder.swift @@ -1,3 +1,31 @@ +#if hasFeature(Embedded) +import SQLKit +import SQLiteNIO + +/// Translates a bound query parameter (an `SQLKit/SQLBindValue`) into an `SQLiteData` value. +/// +/// The Codable/JSON path used elsewhere is unavailable in Embedded Swift, so values are mapped +/// directly from their driver-neutral `SQLKit/SQLDataValue` representation there. +public struct SQLiteDataEncoder: Sendable { + /// Initialize a ``SQLiteDataEncoder``. + public init() {} + + /// Convert the given bound value to an `SQLiteData` value. + /// + /// - Parameter value: The value to convert. + /// - Returns: The converted `SQLiteData` value. + public func encode(_ value: any SQLBindValue & Sendable) throws -> SQLiteData { + switch value.sqlDataValue { + case .integer(let int): return .integer(SQLiteInt64(int)) + case .double(let double): return .float(double) + case .string(let string): return .text(string) + case .blob(let bytes): return .blob(bytes) + case .bool(let bool): return .integer(bool ? 1 : 0) + case .null: return .null + } + } +} +#else #if canImport(NIOCore) import NIOCore #endif @@ -177,3 +205,4 @@ public struct SQLiteDataEncoder: Sendable { } } } +#endif // hasFeature(Embedded) diff --git a/Sources/SQLiteKit/SQLiteRow+SQLRow.swift b/Sources/SQLiteKit/SQLiteRow+SQLRow.swift index 77edf2f..6e30ea0 100644 --- a/Sources/SQLiteKit/SQLiteRow+SQLRow.swift +++ b/Sources/SQLiteKit/SQLiteRow+SQLRow.swift @@ -43,6 +43,9 @@ private struct SQLiteSQLRow: SQLRow { return data == .null } + // `SQLRow.decode(column:as:)` is gated out of the SQLKit protocol in Embedded Swift (generic + // requirement + Decodable). + #if !hasFeature(Embedded) // See `SQLRow.decode(column:as:)`. func decode(column: String, as: D.Type) throws -> D { guard let data = self.row.column(column) else { @@ -50,6 +53,7 @@ private struct SQLiteSQLRow: SQLRow { } return try self.decoder.decode(D.self, from: data) } + #endif } /// A legacy deprecated conformance of `SQLiteRow` directly to `SQLRow`. This interface exists solely @@ -68,6 +72,7 @@ extension SQLiteNIO.SQLiteRow: SQLKit.SQLRow { // See `SQLRow.decodeNil(column:)`. public func decodeNil(column: String) throws -> Bool { (self.column(column) ?? .null) == .null } + #if !hasFeature(Embedded) // Decodable-based row decoding is unavailable in Embedded Swift. // See `SQLRow.decode(column:as:)`. public func decode(column: String, as: D.Type) throws -> D { guard let data = self.column(column) else { throw MissingColumn(column: column) } @@ -87,4 +92,5 @@ extension SQLiteNIO.SQLiteRow: SQLKit.SQLRow { // See `SQLRow.decode(model:with:)`. public func decode(model: D.Type, with: SQLRowDecoder) throws -> D { try with.decode(D.self, from: self) } + #endif // !hasFeature(Embedded) }