Skip to content

Commit 903424a

Browse files
Updated bind parameters
1 parent b21be09 commit 903424a

File tree

4 files changed

+88
-145
lines changed

4 files changed

+88
-145
lines changed

Sources/SQLyra/PreparedStatement.swift

Lines changed: 23 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import SQLite3
66
/// To execute an SQL statement, it must first be compiled into a byte-code program using one of these routines.
77
/// Or, in other words, these routines are constructors for the prepared statement object.
88
public final class PreparedStatement: DatabaseHandle {
9-
@usableFromInline
109
let stmt: OpaquePointer
1110

1211
/// Find the database handle of a prepared statement.
@@ -80,54 +79,6 @@ public final class PreparedStatement: DatabaseHandle {
8079
public func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
8180
try StatementDecoder().decode(type, from: self)
8281
}
83-
84-
// MARK: - String
85-
86-
public func string(at index: Int32) -> String? {
87-
sqlite3_column_text(stmt, index).map { String(cString: $0) }
88-
}
89-
90-
public func string(for name: String) -> String? {
91-
columnIndexByName[name].flatMap { string(at: $0) }
92-
}
93-
94-
// MARK: - Int64
95-
96-
public func int64(at index: Int32) -> Int64 {
97-
sqlite3_column_int64(stmt, index)
98-
}
99-
100-
public func int64(for name: String) -> Int64? {
101-
columnIndexByName[name].map { int64(at: $0) }
102-
}
103-
104-
// MARK: - Double
105-
106-
public func double(at index: Int32) -> Double {
107-
sqlite3_column_double(stmt, index)
108-
}
109-
110-
public func double(for name: String) -> Double? {
111-
columnIndexByName[name].map { double(at: $0) }
112-
}
113-
114-
// MARK: - Blob
115-
116-
public func blob(at index: Int32) -> Data? {
117-
sqlite3_column_blob(stmt, index).map { bytes in
118-
Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index)))
119-
}
120-
}
121-
122-
// MARK: - Null
123-
124-
public func null(at index: Int32) -> Bool {
125-
sqlite3_column_type(stmt, index) == SQLITE_NULL
126-
}
127-
128-
public func null(for name: String) -> Bool {
129-
columnIndexByName[name].map { null(at: $0) } ?? true
130-
}
13182
}
13283

13384
// MARK: - Retrieving Statement SQL
@@ -168,61 +119,41 @@ extension PreparedStatement {
168119
}
169120
}
170121

171-
// MARK: - Bind SQL Parameters
122+
// MARK: - Binding values
172123

173124
private let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
174125

175126
extension PreparedStatement {
176127
@discardableResult
177-
public func bind(name: String, _ parameter: SQLParameter?) throws -> PreparedStatement {
178-
try bind(index: parameterIndex(for: name), parameter)
128+
public func bind(name: String, parameter: SQLParameter) throws -> PreparedStatement {
129+
try bind(index: parameterIndex(for: name), parameter: parameter)
179130
}
180131

181132
@discardableResult
182-
public func bind(index: Int32, _ parameter: SQLParameter?) throws -> PreparedStatement {
183-
var code = SQLITE_OK
184-
switch parameter {
185-
case .none:
186-
code = sqlite3_bind_null(stmt, index)
187-
case .int64(let number)?:
188-
code = sqlite3_bind_int64(stmt, index, number)
189-
case .double(let double)?:
190-
code = sqlite3_bind_double(stmt, index, double)
191-
case .text(let string)?:
192-
code = sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT)
193-
case .blob(let data)?:
194-
code = data.withUnsafeBytes { ptr in
195-
sqlite3_bind_blob(stmt, index, ptr.baseAddress, Int32(data.count), SQLITE_TRANSIENT)
196-
}
133+
public func bind(parameters: SQLParameter...) throws -> PreparedStatement {
134+
for (index, parameter) in parameters.enumerated() {
135+
try bind(index: Int32(index + 1), parameter: parameter)
197136
}
198-
return try check(code)
199-
}
200-
201-
@discardableResult
202-
public func bind(index: Int32, int64: Int64) throws -> PreparedStatement {
203-
try check(sqlite3_bind_int64(stmt, index, int64))
137+
return self
204138
}
205139

206140
@discardableResult
207-
public func bind(index: Int32, double: Double) throws -> PreparedStatement {
208-
try check(sqlite3_bind_double(stmt, index, double))
209-
}
210-
211-
@discardableResult
212-
public func bind(name: String, string: String) throws -> PreparedStatement {
213-
try bind(index: parameterIndex(for: name), string: string)
214-
}
215-
216-
@discardableResult
217-
public func bind(index: Int32, string: String) throws -> PreparedStatement {
218-
try check(sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT))
219-
}
220-
221-
@discardableResult
222-
public func bind(index: Int32, data: Data) throws -> PreparedStatement {
223-
let code = data.withUnsafeBytes { ptr in
224-
sqlite3_bind_blob(stmt, index, ptr.baseAddress, Int32(data.count), SQLITE_TRANSIENT)
225-
}
141+
public func bind(index: Int32, parameter: SQLParameter) throws -> PreparedStatement {
142+
let code =
143+
switch parameter {
144+
case .null:
145+
sqlite3_bind_null(stmt, index)
146+
case .int64(let number):
147+
sqlite3_bind_int64(stmt, index, number)
148+
case .double(let double):
149+
sqlite3_bind_double(stmt, index, double)
150+
case .text(let string):
151+
sqlite3_bind_text(stmt, index, string, -1, SQLITE_TRANSIENT)
152+
case .blob(let data):
153+
data.withUnsafeBytes { ptr in
154+
sqlite3_bind_blob(stmt, index, ptr.baseAddress, Int32(data.count), SQLITE_TRANSIENT)
155+
}
156+
}
226157
return try check(code)
227158
}
228159
}

Sources/SQLyra/SQLParameter.swift

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import Foundation
22

33
/// SQL parameters.
4-
public enum SQLParameter {
4+
public enum SQLParameter: Equatable {
5+
case null
6+
57
/// 64-bit signed integer.
68
case int64(Int64)
79

@@ -11,14 +13,15 @@ public enum SQLParameter {
1113
/// UTF-8 string.
1214
case text(String)
1315

14-
/// Binary data.
16+
/// Binary Large Object.
1517
case blob(Data)
1618

1719
public static func bool(_ value: Bool) -> SQLParameter {
1820
SQLParameter.int64(value ? 1 : 0)
1921
}
2022

21-
public static func int(_ value: Int) -> SQLParameter {
23+
@inlinable
24+
public static func integer<T: SignedInteger>(_ value: T) -> SQLParameter {
2225
SQLParameter.int64(Int64(value))
2326
}
2427
}
@@ -28,20 +31,23 @@ public enum SQLParameter {
2831
extension SQLParameter: CustomStringConvertible {
2932
public var description: String {
3033
switch self {
31-
case .int64(let value):
32-
return value.description
33-
case .double(let value):
34-
return value.description
35-
case .text(let value):
36-
return value.description
37-
case .blob(let value):
38-
return value.description
34+
case .null: "null"
35+
case .int64(let value): value.description
36+
case .double(let value): value.description
37+
case .text(let value): value.description
38+
case .blob(let value): value.description
3939
}
4040
}
4141
}
4242

4343
// MARK: - Literals
4444

45+
extension SQLParameter: ExpressibleByNilLiteral {
46+
public init(nilLiteral: ()) {
47+
self = .null
48+
}
49+
}
50+
4551
extension SQLParameter: ExpressibleByBooleanLiteral {
4652
public init(booleanLiteral value: Bool) {
4753
self = .bool(value)

Tests/SQLyraTests/PreparedStatementTests.swift

Lines changed: 22 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,17 @@ struct Contact: Codable, Equatable, Sendable {
77
let name: String
88
let rating: Double?
99
let image: Data?
10-
}
1110

12-
private let createTableContacts =
13-
"""
14-
CREATE TABLE contacts (
15-
id INT PRIMARY KEY NOT NULL,
16-
name TEXT NOT NULL,
17-
rating FLOAT,
18-
image BLOB
19-
);
20-
"""
11+
static let table = "CREATE TABLE contacts (id INT, name TEXT, rating FLOAT, image BLOB);"
12+
static let insert = "INSERT INTO contacts (id, name, rating, image) VALUES (:id, :name, :rating, :image)"
13+
}
2114

2215
struct PreparedStatementTests {
2316
private let db: Database
2417

2518
init() throws {
26-
db = try Database.open(at: ":memory:", options: [.readwrite, .memory, .extendedResultCode])
27-
try db.execute(createTableContacts)
19+
db = try Database.open(at: ":memory:", options: [.readwrite, .memory])
20+
try db.execute(Contact.table)
2821
}
2922

3023
@Test func sql() throws {
@@ -35,7 +28,7 @@ struct PreparedStatementTests {
3528
}
3629
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name) VALUES (NULL, NULL)")
3730

38-
try insert.bind(name: ":name", "John")
31+
try insert.bind(name: ":name", parameter: "John")
3932
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name) VALUES (NULL, 'John')")
4033
}
4134

@@ -47,52 +40,39 @@ struct PreparedStatementTests {
4740
#expect(insert.parameterIndex(for: ":name") == 2)
4841
}
4942

50-
@Test func clear() throws {
43+
@Test func clearBindings() throws {
5144
let insert = try db.prepare("INSERT INTO contacts (id, name) VALUES (:id, :name)")
5245

53-
try insert.bind(name: ":id", 2).bind(name: ":name", "Bob").execute()
46+
try insert.bind(parameters: 2, "Bob").execute()
5447
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name) VALUES (2, 'Bob')")
5548

5649
try insert.clearBindings()
5750
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name) VALUES (NULL, NULL)")
5851
}
5952

6053
@Test func bind() throws {
61-
let insertContract = "INSERT INTO contacts (id, name, rating, image)"
62-
let insert = try db.prepare("\(insertContract) VALUES (:id, :name, :rating, :image)")
54+
let insert = try db.prepare(Contact.insert)
6355

6456
try insert
65-
.bind(name: ":id", 10)
66-
.bind(name: ":name", "A")
67-
.bind(name: ":rating", 1.0)
68-
.bind(name: ":image", .blob(Data("123".utf8)))
69-
#expect(insert.expandedSQL == "\(insertContract) VALUES (10, 'A', 1.0, x'313233')")
57+
.bind(name: ":id", parameter: 10)
58+
.bind(name: ":name", parameter: "A")
59+
.bind(name: ":rating", parameter: 1.0)
60+
.bind(name: ":image", parameter: .blob(Data("123".utf8)))
61+
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name, rating, image) VALUES (10, 'A', 1.0, x'313233')")
7062

7163
try insert
72-
.bind(index: 1, 20)
73-
.bind(index: 2, "B")
74-
.bind(index: 3, 0.0)
75-
.bind(index: 4, nil)
76-
#expect(insert.expandedSQL == "\(insertContract) VALUES (20, 'B', 0.0, NULL)")
64+
.bind(index: 1, parameter: 20)
65+
.bind(index: 2, parameter: "B")
66+
.bind(index: 3, parameter: 0.0)
67+
.bind(index: 4, parameter: nil)
68+
#expect(insert.expandedSQL == "INSERT INTO contacts (id, name, rating, image) VALUES (20, 'B', 0.0, NULL)")
7769
}
7870

7971
@Test func columns() throws {
80-
let sgl = "INSERT INTO contacts (id, name, rating, image) VALUES (:id, :name, :rating, :image);"
81-
let insert = try db.prepare(sgl)
72+
let insert = try db.prepare(Contact.insert)
8273

83-
try insert
84-
.bind(name: ":id", 5)
85-
.bind(name: ":name", "A")
86-
.bind(name: ":rating", 2.0)
87-
.execute()
88-
.reset()
89-
.clearBindings()
90-
91-
try insert
92-
.bind(name: ":id", 6)
93-
.bind(name: ":name", "B")
94-
.bind(name: ":image", .blob(Data("123".utf8)))
95-
.execute()
74+
try insert.bind(parameters: 5, "A", 2.0, .null).execute().reset()
75+
try insert.bind(parameters: 6, "B", .null, .blob(Data("123".utf8))).execute()
9676

9777
let select = try db.prepare("SELECT * FROM contacts;")
9878
#expect(select.columnCount == 4)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
import SQLyra
3+
import Testing
4+
5+
struct SQLParameterTests {
6+
7+
@Test func literals() {
8+
#expect(SQLParameter.null == nil)
9+
#expect(SQLParameter.bool(true) == true)
10+
#expect(SQLParameter.bool(false) == false)
11+
#expect(SQLParameter.int64(12) == 12)
12+
#expect(SQLParameter.integer(100) == 100)
13+
#expect(SQLParameter.double(1.0) == 1.0)
14+
#expect(SQLParameter.text("hello") == "hello")
15+
}
16+
17+
@Test func description() {
18+
#expect(SQLParameter.null.description == "null")
19+
#expect(SQLParameter.bool(false).description == "0")
20+
#expect(SQLParameter.bool(true).description == "1")
21+
#expect(SQLParameter.int64(100).description == "100")
22+
#expect(SQLParameter.double(1.0).description == "1.0")
23+
#expect(SQLParameter.text("row").description == "row")
24+
#expect(SQLParameter.blob(Data("abc".utf8)).description == "3 bytes")
25+
}
26+
}

0 commit comments

Comments
 (0)