Skip to content

Commit 1cd7b92

Browse files
committed
WIP: Support binding by interpolation
This adds support for quoting/binding values to statements by interpolation. db.prepare("SELECT * FROM users WHERE admin = \(true)" as quoted) // SELECT * FROM users WHERE admin = 1 Signed-off-by: Stephen Celis <stephen@stephencelis.com>
1 parent 8749e2e commit 1cd7b92

File tree

3 files changed

+85
-2
lines changed

3 files changed

+85
-2
lines changed

SQLite Tests/DatabaseTests.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class DatabaseTests: SQLiteTestCase {
5151
db.prepare("SELECT * FROM users WHERE admin = ?", 0)
5252
db.prepare("SELECT * FROM users WHERE admin = ?", [0])
5353
db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0])
54+
db.prepare("SELECT * FROM users WHERE admin = \(false)" as quoted)
5455
// no-op assert-nothing-asserted
5556
}
5657

@@ -59,15 +60,21 @@ class DatabaseTests: SQLiteTestCase {
5960
db.run("SELECT * FROM users WHERE admin = ?", 0)
6061
db.run("SELECT * FROM users WHERE admin = ?", [0])
6162
db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0])
62-
AssertSQL("SELECT * FROM users WHERE admin = 0", 4)
63+
db.run("SELECT * FROM users WHERE admin = \(false)" as quoted)
64+
AssertSQL("SELECT * FROM users WHERE admin = 0", 5)
65+
66+
var age: Int?
67+
db.run("SELECT * FROM users WHERE age IS \(age)" as quoted)
68+
AssertSQL("SELECT * FROM users WHERE age IS NULL")
6369
}
6470

6571
func test_scalar_preparesRunsAndReturnsScalarValues() {
6672
XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as! Int64)
6773
XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as! Int64)
6874
XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as! Int64)
6975
XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as! Int64)
70-
AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4)
76+
XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = \(false)" as quoted) as! Int64)
77+
AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 5)
7178
}
7279

7380
func test_transaction_executesBeginDeferred() {

SQLite/Database.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ public final class Database {
115115
return prepare(statement).bind(bindings)
116116
}
117117

118+
/// Prepares a single SQL statement and binds parameters to it.
119+
///
120+
/// :param: statement A SQL string interpolated with quoted values.
121+
///
122+
/// :returns: The statement.
123+
public func prepare(statement: quoted) -> Statement {
124+
return prepare(statement.SQL).bind(statement.bindings)
125+
}
126+
118127
// MARK: - Run
119128

120129
/// Runs a single SQL statement (with optional parameter bindings).
@@ -151,6 +160,15 @@ public final class Database {
151160
return prepare(statement).run(bindings)
152161
}
153162

163+
/// Prepares, binds, and runs a single SQL statement.
164+
///
165+
/// :param: statement A SQL string interpolated with quoted values.
166+
///
167+
/// :returns: The statement.
168+
public func run(statement: quoted) -> Statement {
169+
return prepare(statement.SQL).run(statement.bindings)
170+
}
171+
154172
// MARK: - Scalar
155173

156174
/// Runs a single SQL statement (with optional parameter bindings),
@@ -190,6 +208,16 @@ public final class Database {
190208
return prepare(statement).scalar(bindings)
191209
}
192210

211+
/// Prepares, binds, and runs a single SQL statement, returning the first
212+
/// value of the first row.
213+
///
214+
/// :param: statement A SQL string interpolated with quoted values.
215+
///
216+
/// :returns: The first value of the first row returned.
217+
public func scalar(statement: quoted) -> Binding? {
218+
return prepare(statement.SQL).scalar(statement.bindings)
219+
}
220+
193221
// MARK: - Transactions
194222

195223
/// The mode in which a transaction acquires a lock.

SQLite/Statement.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,54 @@ extension Statement: Printable {
230230

231231
}
232232

233+
extension Statement {
234+
235+
public struct Interpolation {
236+
237+
internal let SQL: String
238+
239+
internal let bindings: [Binding?]
240+
241+
private init(literal SQL: String = "", _ bindings: [Binding?] = []) {
242+
(self.SQL, self.bindings) = (SQL, bindings)
243+
}
244+
245+
}
246+
247+
}
248+
249+
// MARK: - StringInterpolationConvertible
250+
extension Statement.Interpolation: StringInterpolationConvertible {
251+
252+
public init(stringInterpolation components: Statement.Interpolation...) {
253+
var (SQL, bindings) = ("", [Binding?]())
254+
for (idx, component) in enumerate(components) {
255+
if idx % 2 == 0 {
256+
SQL += component.bindings.first as! String
257+
} else {
258+
SQL += component.SQL
259+
bindings.extend(component.bindings)
260+
}
261+
}
262+
self.init(literal: SQL, bindings)
263+
}
264+
265+
public init<T: Value>(stringInterpolationSegment value: T) {
266+
self.init(literal: "?", [value.datatypeValue])
267+
}
268+
269+
public init<T: Value>(stringInterpolationSegment value: T?) {
270+
self.init(literal: "?", [value?.datatypeValue])
271+
}
272+
273+
public init<T>(stringInterpolationSegment expr: T) {
274+
self.init(literal: "?", [toString(expr)])
275+
}
276+
277+
}
278+
279+
public typealias quoted = Statement.Interpolation
280+
233281
public func && (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement {
234282
if lhs.status == SQLITE_OK { lhs.run() }
235283
return lhs.failed ? lhs : rhs()

0 commit comments

Comments
 (0)