Skip to content

Commit

Permalink
Implement automatically binding string-interpolated values (#56)
Browse files Browse the repository at this point in the history
* Implement autom-binding with string interpolation

* Implement automatic binding in existing `raw` method

This removes the old `bind()` API.

* Update to auto-generated Linux test file

* Clean up and make Fragement internal
  • Loading branch information
tkrajacic authored and tanner0101 committed Apr 26, 2019
1 parent e282a4c commit 159322e
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 63 deletions.
49 changes: 9 additions & 40 deletions Sources/SQLKit/Builders/SQLRawBuilder.swift
Original file line number Diff line number Diff line change
@@ -1,55 +1,24 @@
/// Builds raw SQL queries.
///
/// conn.raw("SELECT * FROM planets WHERE name = ?")
/// .bind("Earth")
/// conn.raw("SELECT * FROM planets WHERE name = \(bind: "Earth")")
/// .all(decoding: Planet.self)
///
public final class SQLRawBuilder: SQLQueryBuilder, SQLQueryFetcher {
/// Raw query being built.
public var sql: String

/// Bound values.
public var binds: [Encodable]

var sql: SQLQueryString

/// See `SQLQueryBuilder`.
public var database: SQLDatabase

/// See `SQLQueryBuilder`.
public var query: SQLExpression {
return SQLRaw(self.sql)
return self.sql
}

/// Creates a new `SQLRawBuilder`.
public init(_ sql: String, on database: SQLDatabase) {
self.sql = sql
public init(_ sql: SQLQueryString, on database: SQLDatabase) {
self.database = database
self.binds = []
}

/// Binds a single encodable value to the query. Each bind should
/// correspond to a placeholder in the query string.
///
/// conn.raw("SELECT * FROM planets WHERE name = ?")
/// .bind("Earth")
/// .all(decoding: Planet.self)
///
/// This method can be chained multiple times.
public func bind(_ encodable: Encodable) -> Self {
self.binds.append(encodable)
return self
}

/// Binds an array of encodable values to the query. Each item in the
/// array should correspond to a placeholder in the query string.
///
/// conn.raw("SELECT * FROM planets WHERE name = ? OR name = ?")
/// .binds(["Earth", "Mars"])
/// .all(decoding: Planet.self)
///
/// This method can be chained multiple times.
public func binds(_ encodables: [Encodable]) -> Self {
self.binds += encodables
return self
self.sql = sql
}
}

Expand All @@ -63,7 +32,7 @@ extension SQLDatabase {
/// - parameters:
/// - sql: The SQL query string.
/// - returns: `SQLRawBuilder`.
public func raw(_ sql: String) -> SQLRawBuilder {
public func raw(_ sql: SQLQueryString) -> SQLRawBuilder {
return .init(sql, on: self)
}
}
53 changes: 53 additions & 0 deletions Sources/SQLKit/Query/SQLQueryString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
public struct SQLQueryString {
enum Fragment {
case literal(String)
case value(Encodable)
}

var fragments: [Fragment]
}

extension SQLQueryString: ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
fragments = [.literal(value)]
}
}

extension SQLQueryString: ExpressibleByStringInterpolation {

public init(stringInterpolation: SQLQueryString) {
fragments = stringInterpolation.fragments
}
}

extension SQLQueryString: StringInterpolationProtocol {
public init(literalCapacity: Int, interpolationCount: Int) {
fragments = []
}

mutating public func appendLiteral(_ literal: String) {
fragments.append(.literal(literal))
}

mutating public func appendInterpolation(_ literal: String) {
fragments.append(.literal(literal))
}

mutating public func appendInterpolation(bind value: Encodable) {
fragments.append(.value(value))
}
}

extension SQLQueryString: SQLExpression {
public func serialize(to serializer: inout SQLSerializer) {
for fragment in fragments {
switch fragment {
case let .literal(str):
serializer.write(str)
case let .value(v):
serializer.dialect.nextBindPlaceholder().serialize(to: &serializer)
serializer.binds.append(v)
}
}
}
}
26 changes: 3 additions & 23 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -1,28 +1,8 @@
import XCTest

@testable import SQLKitTests
import SQLKitTests

// MARK: SQLKitTests
var tests = [XCTestCaseEntry]()
tests += SQLKitTests.__allTests()

extension SQLKitTests {
static let __allSQLKitTestsTests = [
("testBenchmarker", testBenchmarker),
("testLockingClause_forUpdate", testLockingClause_forUpdate),
("testLockingClause_lockInShareMode", testLockingClause_lockInShareMode),
]
}

// MARK: Test Runner

#if !os(macOS)
public func __buildTestEntries() -> [XCTestCaseEntry] {
return [
// SQLKitTests
testCase(SQLKitTests.__allSQLKitTestsTests),
]
}

let tests = __buildTestEntries()
XCTMain(tests)
#endif

11 changes: 11 additions & 0 deletions Tests/SQLKitTests/SQLKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,15 @@ final class SQLKitTests: XCTestCase {
.run().wait()
XCTAssertEqual(db.results[0], "SELECT * FROM `planets` WHERE `name` = ? LOCK IN SHARE MODE")
}

func testRawQueryStringInterpolation() throws {
let db = TestDatabase()
let (table, planet) = ("planets", "Earth")
let builder = db.raw("SELECT * FROM \(table) WHERE name = \(bind: planet)")
var serializer = SQLSerializer(dialect: GenericDialect())
builder.query.serialize(to: &serializer)

XCTAssertEqual(serializer.sql, "SELECT * FROM planets WHERE name = ?")
XCTAssert(serializer.binds.first! as! String == "Earth")
}
}
21 changes: 21 additions & 0 deletions Tests/SQLKitTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#if !canImport(ObjectiveC)
import XCTest

extension SQLKitTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SQLKitTests = [
("testBenchmarker", testBenchmarker),
("testLockingClause_forUpdate", testLockingClause_forUpdate),
("testLockingClause_lockInShareMode", testLockingClause_lockInShareMode),
("testRawQueryStringInterpolation", testRawQueryStringInterpolation),
]
}

public func __allTests() -> [XCTestCaseEntry] {
return [
testCase(SQLKitTests.__allTests__SQLKitTests),
]
}
#endif

0 comments on commit 159322e

Please sign in to comment.