From 702662ef283cb4f439ec19b2ba47d0f3f3e35540 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 7 Feb 2026 15:56:36 +0100 Subject: [PATCH 1/3] basic query implementation --- Package.resolved | 6 +-- Package.swift | 3 +- .../SQLiteDatabaseConnection.swift | 41 ++++++++++++++++-- .../FeatherSQLiteDatabaseTestSuite.swift | 43 +++++++++---------- 4 files changed, 63 insertions(+), 30 deletions(-) diff --git a/Package.resolved b/Package.resolved index 33959b4..2f6c459 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "89777d929ec26bbed6f64b140dce168ca0fbc69d9273ed1edcfaf674a6bb8eb4", + "originHash" : "1dd4c76780e6105a7aae9aef03244ddd87c1bf687f0e908cc13a8a7fe93f7128", "pins" : [ { "identity" : "feather-database", "kind" : "remoteSourceControl", "location" : "https://github.com/feather-framework/feather-database", "state" : { - "revision" : "4ef69e67018c4bdf843858e8976c13b97c3afe4c", - "version" : "1.0.0-beta.3" + "branch" : "feature/query-type", + "revision" : "a47dc8198f2427349156cc3619e017b75a9c584a" } }, { diff --git a/Package.swift b/Package.swift index 016c569..948c3ea 100644 --- a/Package.swift +++ b/Package.swift @@ -45,8 +45,9 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-log", from: "1.6.0"), .package(url: "https://github.com/vapor/sqlite-nio", from: "1.12.0"), - .package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"), .package(url: "https://github.com/swift-server/swift-service-lifecycle", from: "2.8.0"), +// .package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"), + .package(url: "https://github.com/feather-framework/feather-database", branch: "feature/query-type"), // [docc-plugin-placeholder] ], targets: [ diff --git a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift index 1db5ffa..96c600b 100644 --- a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift +++ b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift @@ -9,9 +9,43 @@ import FeatherDatabase import Logging import SQLiteNIO +extension Query { + + fileprivate struct SQLiteQuery { + var sql: String + var bindings: [SQLiteData] + } + + fileprivate func toSQLiteQuery() -> SQLiteQuery { + var sqliteSQL = sql + var sqliteBindings: [SQLiteData] = [] + + for binding in bindings { + /// postgres binding index starts with 1 + let idx = binding.index + 1 + sqliteSQL = + sqliteSQL + .replacing("{{\(idx)}}", with: "?") + + switch binding.binding { + case .int(let value): + sqliteBindings.append(.integer(value)) + case .double(let value): + sqliteBindings.append(.float(value)) + case .string(let value): + sqliteBindings.append(.text(value)) + } + } + + return .init( + sql: sqliteSQL, + bindings: sqliteBindings + ) + } +} + public struct SQLiteDatabaseConnection: DatabaseConnection { - public typealias Query = SQLiteDatabaseQuery public typealias RowSequence = SQLiteDatabaseRowSequence var connection: SQLiteConnection @@ -31,9 +65,10 @@ public struct SQLiteDatabaseConnection: DatabaseConnection { _ handler: (RowSequence) async throws -> T = { $0 } ) async throws(DatabaseError) -> T { do { + let sqliteQuery = query.toSQLiteQuery() let result = try await connection.query( - query.sql, - query.bindings + sqliteQuery.sql, + sqliteQuery.bindings ) return try await handler( SQLiteDatabaseRowSequence( diff --git a/Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift b/Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift index 0b5b853..0106e5c 100644 --- a/Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift +++ b/Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift @@ -273,18 +273,15 @@ struct FeatherSQLiteDatabaseTestSuite { """# ) - let insert = SQLiteDatabaseQuery( - unsafeSQL: #""" + try await connection.run( + query: #""" INSERT INTO "widgets" ("id", "name") VALUES - (?, ?); - """#, - bindings: [.integer(1), .text("gizmo")] + (\#(1), \#("gizmo")); + """# ) - try await connection.run(query: insert) - let result = try await connection.run( query: #""" @@ -317,14 +314,15 @@ struct FeatherSQLiteDatabaseTestSuite { ) let body: String? = nil - let insert: SQLiteDatabaseQuery = #""" - INSERT INTO "notes" - ("id", "body") - VALUES - (1, \#(body)); - """# - try await connection.run(query: insert) + try await connection.run( + query: #""" + INSERT INTO "notes" + ("id", "body") + VALUES + (1, \#(body)); + """# + ) let result = try await connection.run( @@ -357,15 +355,14 @@ struct FeatherSQLiteDatabaseTestSuite { """# ) - let label: SQLiteData = .text("alpha") - let insert: SQLiteDatabaseQuery = #""" - INSERT INTO "tags" - ("id", "label") - VALUES - (1, \#(label)); - """# - - try await connection.run(query: insert) + try await connection.run( + query: #""" + INSERT INTO "tags" + ("id", "label") + VALUES + (1, \#("alpha")); + """# + ) let result = try await connection.run( From 4b5a7176e05cd773af65eff9e7b2b24d589e9f2f Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 7 Feb 2026 16:28:47 +0100 Subject: [PATCH 2/3] remove unused code --- README.md | 6 +- .../SQLiteDatabaseConnection.swift | 1 - .../SQLiteDatabaseQuery.swift | 199 ------------------ 3 files changed, 3 insertions(+), 203 deletions(-) delete mode 100644 Sources/FeatherSQLiteDatabase/SQLiteDatabaseQuery.swift diff --git a/README.md b/README.md index 7ac1506..805357e 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ SQLite driver implementation for the abstract [Feather Database](https://github.com/feather-framework/feather-database) Swift API package. [ - ![Release: 1.0.0-beta.4](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E4-F05138) + ![Release: 1.0.0-beta.5](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E5-F05138) ]( - https://github.com/feather-framework/feather-sqlite-database/releases/tag/1.0.0-beta.4 + https://github.com/feather-framework/feather-sqlite-database/releases/tag/1.0.0-beta.5 ) ## Features @@ -36,7 +36,7 @@ SQLite driver implementation for the abstract [Feather Database](https://github. Add the dependency to your `Package.swift`: ```swift -.package(url: "https://github.com/feather-framework/feather-sqlite-database", exact: "1.0.0-beta.4"), +.package(url: "https://github.com/feather-framework/feather-sqlite-database", exact: "1.0.0-beta.5"), ``` Then add `FeatherSQLiteDatabase` to your target dependencies: diff --git a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift index 96c600b..37fb760 100644 --- a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift +++ b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift @@ -21,7 +21,6 @@ extension Query { var sqliteBindings: [SQLiteData] = [] for binding in bindings { - /// postgres binding index starts with 1 let idx = binding.index + 1 sqliteSQL = sqliteSQL diff --git a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseQuery.swift b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseQuery.swift deleted file mode 100644 index e845f76..0000000 --- a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseQuery.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// SQLiteDatabaseQuery.swift -// feather-sqlite-database -// -// Created by Tibor Bödecs on 2026. 01. 10. -// - -import FeatherDatabase -import SQLiteNIO - -/// A SQLite query with SQL text and bound parameters. -/// -/// Use this type to construct SQLite queries safely. -public struct SQLiteDatabaseQuery: DatabaseQuery { - /// The SQL text to execute. - /// - /// This is the raw SQL string for the query. - public var sql: String - /// The bound parameters for the SQL text. - /// - /// These values are passed alongside `sql`. - public var bindings: [SQLiteData] - - /// Create a query from raw SQL and bindings. - /// - /// Prefer string interpolation initializers when possible to bind values. - /// - Parameters: - /// - sql: The raw SQL string to execute. - /// - bindings: The bound parameters for the SQL. - public init( - unsafeSQL sql: String, - bindings: [SQLiteData] = [] - ) { - self.sql = sql - self.bindings = bindings - } -} - -extension SQLiteDatabaseQuery: ExpressibleByStringInterpolation { - - /// A string interpolation builder for SQLite queries. - /// - /// Use interpolation to bind values safely into SQL text. - public struct StringInterpolation: StringInterpolationProtocol, Sendable { - - /// The string literal type used by the interpolation. - /// - /// This matches the standard `String` literal type. - public typealias StringLiteralType = String - - @usableFromInline - var sql: String - - @usableFromInline - var binds: [SQLiteData] - - /// Create a new interpolation buffer. - /// - /// Use the provided capacities to preallocate storage. - /// - Parameters: - /// - literalCapacity: The expected literal character count. - /// - interpolationCount: The expected number of interpolations. - public init( - literalCapacity: Int, - interpolationCount: Int - ) { - self.sql = "" - self.sql.reserveCapacity(literalCapacity) - self.binds = [] - self.binds.reserveCapacity(interpolationCount) - } - - /// Append a literal string segment. - /// - /// This adds raw SQL text to the builder. - /// - Parameter literal: The literal string segment. - public mutating func appendLiteral( - _ literal: String - ) { - self.sql.append(contentsOf: literal) - } - - @inlinable - /// Append an interpolated optional string value. - /// - /// Non-nil values are bound, and nil values emit `NULL`. - /// - Parameter value: The optional string value to interpolate. - /// - Returns: Nothing. - public mutating func appendInterpolation( - _ value: String? - ) { - switch value { - case .some(let value): - self.binds.append(.text(value)) - self.sql.append(contentsOf: "?") - case .none: - self.sql.append(contentsOf: "NULL") - } - } - - @inlinable - /// Append an interpolated integer value. - /// - /// The value is bound as a SQLite integer. - /// - Parameter value: The integer value to interpolate. - /// - Returns: Nothing. - public mutating func appendInterpolation( - _ value: Int - ) { - self.binds.append(.integer(value)) - self.sql.append(contentsOf: "?") - } - - @inlinable - /// Append an interpolated floating-point value. - /// - /// The value is bound as a SQLite float. - /// - Parameter value: The double value to interpolate. - /// - Returns: Nothing. - public mutating func appendInterpolation( - _ value: Double - ) { - self.binds.append(.float(value)) - self.sql.append(contentsOf: "?") - } - - @inlinable - /// Append an interpolated string value. - /// - /// The value is bound as a SQLite text value. - /// - Parameter value: The string value to interpolate. - /// - Returns: Nothing. - public mutating func appendInterpolation( - _ value: String - ) { - self.binds.append(.text(value)) - self.sql.append(contentsOf: "?") - } - - @inlinable - /// Append an interpolated SQLite data value. - /// - /// The value is bound directly as SQLite data. - /// - Parameter value: The SQLite data value to interpolate. - /// - Returns: Nothing. - public mutating func appendInterpolation( - _ value: SQLiteData - ) { - self.binds.append(value) - self.sql.append(contentsOf: "?") - } - - // @inlinable - // public mutating func appendInterpolation(_ value: SQLiteData?) throws { - // switch value { - // case .none: - // self.binds.append(.null) - // case .some(let value): - // self.binds.append(value) - // } - // - // self.sql.append(contentsOf: "?") - // } - - @inlinable - /// Append an unescaped SQL fragment. - /// - /// Use this only for trusted identifiers or SQL keywords. - /// - Parameter interpolated: The raw SQL fragment to insert. - /// - Returns: Nothing. - public mutating func appendInterpolation( - unescaped interpolated: String - ) { - self.sql.append(contentsOf: interpolated) - } - } - - /// Create a query from a string interpolation builder. - /// - /// This initializer is used by Swift string interpolation. - /// - Parameter stringInterpolation: The interpolation builder. - public init( - stringInterpolation: StringInterpolation - ) { - self.sql = stringInterpolation.sql - self.bindings = stringInterpolation.binds - } - - /// Create a query from a string literal. - /// - /// This initializer does not add any bindings. - /// - Parameter value: The literal SQL string. - public init( - stringLiteral value: String - ) { - self.sql = value - self.bindings = [] - } -} From b81d4a35791603d2ddbae26dc7e6caa99f9cc693 Mon Sep 17 00:00:00 2001 From: Tibor Bodecs Date: Sat, 7 Feb 2026 17:35:25 +0100 Subject: [PATCH 3/3] prep for release --- Package.resolved | 6 +++--- Package.swift | 3 +-- .../FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Package.resolved b/Package.resolved index 2f6c459..afe3c94 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "1dd4c76780e6105a7aae9aef03244ddd87c1bf687f0e908cc13a8a7fe93f7128", + "originHash" : "9477039766900a876852733dc60c616e209d09ef46f39d32ca540a12c6ec7b4b", "pins" : [ { "identity" : "feather-database", "kind" : "remoteSourceControl", "location" : "https://github.com/feather-framework/feather-database", "state" : { - "branch" : "feature/query-type", - "revision" : "a47dc8198f2427349156cc3619e017b75a9c584a" + "revision" : "8bd475b24dcf18b9b03534c99c5ccf626a8d38b9", + "version" : "1.0.0-beta.4" } }, { diff --git a/Package.swift b/Package.swift index 948c3ea..9bdbd56 100644 --- a/Package.swift +++ b/Package.swift @@ -46,8 +46,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-log", from: "1.6.0"), .package(url: "https://github.com/vapor/sqlite-nio", from: "1.12.0"), .package(url: "https://github.com/swift-server/swift-service-lifecycle", from: "2.8.0"), -// .package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"), - .package(url: "https://github.com/feather-framework/feather-database", branch: "feature/query-type"), + .package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.4"), // [docc-plugin-placeholder] ], targets: [ diff --git a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift index 37fb760..46e9490 100644 --- a/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift +++ b/Sources/FeatherSQLiteDatabase/SQLiteDatabaseConnection.swift @@ -9,7 +9,7 @@ import FeatherDatabase import Logging import SQLiteNIO -extension Query { +extension DatabaseQuery { fileprivate struct SQLiteQuery { var sql: String @@ -60,7 +60,7 @@ public struct SQLiteDatabaseConnection: DatabaseConnection { /// - Returns: A query result containing the returned rows. @discardableResult public func run( - query: Query, + query: DatabaseQuery, _ handler: (RowSequence) async throws -> T = { $0 } ) async throws(DatabaseError) -> T { do {