Skip to content

Commit

Permalink
SQL statements with custom string interpolation.
Browse files Browse the repository at this point in the history
  • Loading branch information
DnV1eX committed Jun 23, 2021
1 parent 83c3454 commit 888a26d
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 1 deletion.
53 changes: 53 additions & 0 deletions Sources/SQLiteCombine/SQLiteCombine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,15 @@ public final class SQLite {
Publisher(db: db, sql: sql, values: values)
}

public func publisher<Output>(_ statement: Statement) -> Publisher<Output> {
Publisher(db: db, sql: statement.sql, values: statement.values)
}

public func publisher<Output>(_ statement: Statement, outputType: Output.Type) -> Publisher<Output> {
Publisher(db: db, sql: statement.sql, values: statement.values)
}


public func trace(events: TraceEvents = []) {

sqlite3_trace_v2(db, UInt32(events.rawValue), { event, _, p, x in
Expand Down Expand Up @@ -268,6 +276,51 @@ extension SQLite.Publisher {
}


public extension SQLite {

struct Statement: ExpressibleByStringInterpolation {

public struct StringInterpolation: StringInterpolationProtocol {

@usableFromInline var sql: String = ""
@usableFromInline var values: [SQLiteValue?] = []

@inlinable public init(literalCapacity: Int, interpolationCount: Int) {
sql.reserveCapacity(literalCapacity + interpolationCount * 3)
values.reserveCapacity(interpolationCount)
}

@inlinable public mutating func appendLiteral(_ literal: StringLiteralType) {
sql += literal
}

@inlinable public mutating func appendInterpolation(_ parameters: SQLiteValue?...) {
guard !parameters.isEmpty else { return }
sql += repeatElement("?", count: parameters.count).joined(separator: ",")
values += parameters
}

@inlinable public mutating func appendInterpolation<S: StringProtocol>(K identifiers: S...) {
sql += identifiers.map { "\"\($0)\"" }.joined(separator: ",")
}
}

let sql: String
let values: [SQLiteValue?]

public init(stringLiteral value: StringLiteralType) {
sql = value
values = []
}

public init(stringInterpolation: StringInterpolation) {
sql = stringInterpolation.sql
values = stringInterpolation.values
}
}
}



extension Result where Success == SQLite.DBResult, Failure == SQLite.DBError {

Expand Down
78 changes: 77 additions & 1 deletion Tests/SQLiteCombineTests/SQLiteCombineTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ final class SQLiteCombineTests: XCTestCase {


@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
func testMultipleStatements() throws {
func testMultipleRequests() throws {

let insertCompletionExpectation = expectation(description: "Insert completion")
let insertValueExpectation = expectation(description: "Insert value")
Expand Down Expand Up @@ -323,6 +323,82 @@ final class SQLiteCombineTests: XCTestCase {

waitForExpectations(timeout: 0, handler: nil)
}


func testParameters() throws {

let insertCompletionExpectation = expectation(description: "Insert completion")
let insertValueExpectation = expectation(description: "Insert value")
_ = db.publisher(sql: "INSERT INTO test VALUES (?, :two, :two, ?, ?1)", 0.1, 0.2, 0.3)
.sink { completion in
if case let .failure(error) = completion { XCTFail(String(describing: error)) }
insertCompletionExpectation.fulfill()
} receiveValue: {
insertValueExpectation.fulfill()
}

let selectCompletionExpectation = expectation(description: "Select completion")
let selectValueExpectation = expectation(description: "Select value")
_ = db.publisher(sql: "SELECT one, two, three, four, five FROM test")
.sink { completion in
if case let .failure(error) = completion { XCTFail(String(describing: error)) }
selectCompletionExpectation.fulfill()
} receiveValue: { (row: [Double]) in
XCTAssertEqual(row, [0.1, 0.2, 0.2, 0.3, 0.1])
selectValueExpectation.fulfill()
}

waitForExpectations(timeout: 0, handler: nil)
}


func testStatement() throws {

let table = "test"
let columns = ("one", "two", "three", "four", "five")

let insertCompletionExpectation = expectation(description: "Insert completion")
let insertValueExpectation = expectation(description: "Insert value")
_ = db.publisher("INSERT INTO \(K: table) VALUES (\(1, 2.0, "3", Data([0x04]), nil))")
.sink { completion in
if case let .failure(error) = completion { XCTFail(String(describing: error)) }
insertCompletionExpectation.fulfill()
} receiveValue: {
insertValueExpectation.fulfill()
}

let selectCompletionExpectation = expectation(description: "Select completion")
let selectValueExpectation = expectation(description: "Select value")
_ = db.publisher("SELECT * FROM test", outputType: (int: Int, double: Double, string: String, data: Data, optional: Any?).self)
.sink { completion in
if case let .failure(error) = completion { XCTFail(String(describing: error)) }
selectCompletionExpectation.fulfill()
} receiveValue: { row in
XCTAssertEqual(row.int, 1)
XCTAssertEqual(row.double, 2.0)
XCTAssertEqual(row.string, "3")
XCTAssertEqual(row.data, Data([0x04]))
XCTAssertNil(row.optional)
selectValueExpectation.fulfill()
}

let selectImplicitCompletionExpectation = expectation(description: "Select implicit completion")
let selectImplicitValueExpectation = expectation(description: "Select implicit value")
_ = db.publisher("SELECT \(K: columns.0, columns.1, columns.2, columns.3, columns.4) FROM \(K: table) WHERE \(K: columns.0)=\(1) AND \(K: columns.1)=\(2.0) AND \(K: columns.2)=\("3") AND \(K: columns.3)=\(Data([0x04])) AND \(K: columns.4) IS \(nil)")
.sink { completion in
if case let .failure(error) = completion { XCTFail(String(describing: error)) }
selectImplicitCompletionExpectation.fulfill()
} receiveValue: { (int: Int, double: Double, string: String, data: Data, optional: Any?) in
XCTAssertEqual(int, 1)
XCTAssertEqual(double, 2.0)
XCTAssertEqual(string, "3")
XCTAssertEqual(data, Data([0x04]))
XCTAssertNil(optional)
selectImplicitValueExpectation.fulfill()
}

waitForExpectations(timeout: 0, handler: nil)
}
}


Expand Down

0 comments on commit 888a26d

Please sign in to comment.