Skip to content

Commit 7b284da

Browse files
Merge pull request #10 from Alexander-Ignition/decoding-row
Create Row and RowDecoder
2 parents 3b0838d + bbb9518 commit 7b284da

File tree

11 files changed

+664
-296
lines changed

11 files changed

+664
-296
lines changed

Playgrounds/README.playground/Contents.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
[Documentation](https://alexander-ignition.github.io/SQLyra/documentation/sqlyra/)
1111

12-
- Note: this readme file is available as Xcode playground in Playgrounds/README.playground
12+
> this readme file is available as Xcode playground in Playgrounds/README.playground
1313

1414
## Open
1515

@@ -26,21 +26,21 @@ let database = try Database.open(
2626

2727
Create table for contacts with fields `id` and `name`.
2828
*/
29-
try database.execute(
30-
"""
29+
let sql = """
3130
CREATE TABLE contacts(
3231
id INT PRIMARY KEY NOT NULL,
3332
name TEXT
3433
);
3534
"""
36-
)
35+
try database.execute(sql)
3736
/*:
3837
## Insert
3938

4039
Insert new contacts Paul and John.
4140
*/
42-
try database.execute("INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
43-
try database.execute("INSERT INTO contacts (id, name) VALUES (2, 'John');")
41+
let insert = try database.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
42+
try insert.bind(parameters: 1, "Paul").execute().reset()
43+
try insert.bind(parameters: 2, "John").execute()
4444
/*:
4545
## Select
4646

@@ -51,4 +51,5 @@ struct Contact: Codable {
5151
let name: String
5252
}
5353

54-
let contacts = try database.prepare("SELECT * FROM contacts;").array(decoding: Contact.self)
54+
let select = try database.prepare("SELECT * FROM contacts;")
55+
let contacts = try select.array(Contact.self)

README.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Swift SQLite wrapper.
88

99
[Documentation](https://alexander-ignition.github.io/SQLyra/documentation/sqlyra/)
1010

11-
- Note: this readme file is available as Xcode playground in Playgrounds/README.playground
11+
> this readme file is available as Xcode playground in Playgrounds/README.playground
1212
1313
## Open
1414

@@ -25,21 +25,21 @@ let database = try Database.open(
2525

2626
Create table for contacts with fields `id` and `name`.
2727
```swift
28-
try database.execute(
29-
"""
28+
let sql = """
3029
CREATE TABLE contacts(
3130
id INT PRIMARY KEY NOT NULL,
3231
name TEXT
3332
);
3433
"""
35-
)
34+
try database.execute(sql)
3635
```
3736
## Insert
3837

3938
Insert new contacts Paul and John.
4039
```swift
41-
try database.execute("INSERT INTO contacts (id, name) VALUES (1, 'Paul');")
42-
try database.execute("INSERT INTO contacts (id, name) VALUES (2, 'John');")
40+
let insert = try database.prepare("INSERT INTO contacts (id, name) VALUES (?, ?);")
41+
try insert.bind(parameters: 1, "Paul").execute().reset()
42+
try insert.bind(parameters: 2, "John").execute()
4343
```
4444
## Select
4545

@@ -50,5 +50,6 @@ struct Contact: Codable {
5050
let name: String
5151
}
5252

53-
let contacts = try database.prepare("SELECT * FROM contacts;").array(decoding: Contact.self)
53+
let select = try database.prepare("SELECT * FROM contacts;")
54+
let contacts = try select.array(Contact.self)
5455
```

Sources/SQLyra/DatabaseError.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public struct DatabaseError: Error, Equatable, Hashable {
77
public let code: Int32
88

99
/// A short error description.
10-
public var message: String?
10+
public let message: String?
1111

1212
/// A complete sentence (or more) describing why the operation failed.
1313
public let details: String?

Sources/SQLyra/PreparedStatement.swift

Lines changed: 95 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ public final class PreparedStatement: DatabaseHandle {
1111
/// Find the database handle of a prepared statement.
1212
var db: OpaquePointer! { sqlite3_db_handle(stmt) }
1313

14-
private(set) lazy var columnIndexByName = [String: Int32](
14+
private(set) lazy var columnIndexByName = [String: Int](
1515
uniqueKeysWithValues: (0..<columnCount).compactMap { index in
16-
column(at: index).name.map { name in (name, index) }
16+
columnName(at: index).map { name in (name, index) }
1717
}
1818
)
1919

@@ -34,20 +34,6 @@ public final class PreparedStatement: DatabaseHandle {
3434
try check(sqlite3_step(stmt), SQLITE_DONE)
3535
}
3636

37-
/// The new row of data is ready for processing.
38-
///
39-
/// - Throws: ``DatabaseError``
40-
public func step() throws -> Bool {
41-
switch sqlite3_step(stmt) {
42-
case SQLITE_DONE:
43-
return false
44-
case SQLITE_ROW:
45-
return true
46-
case let code:
47-
throw DatabaseError(code: code, db: db)
48-
}
49-
}
50-
5137
/// Reset the prepared statement.
5238
///
5339
/// The ``PreparedStatement/reset()`` function is called to reset a prepared statement object back to its initial state, ready to be re-executed.
@@ -59,32 +45,6 @@ public final class PreparedStatement: DatabaseHandle {
5945
public func reset() throws -> PreparedStatement {
6046
try check(sqlite3_reset(stmt))
6147
}
62-
63-
/// Reset all bindings on a prepared statement.
64-
///
65-
/// Contrary to the intuition of many, ``PreparedStatement/reset()`` does not reset the bindings on a prepared statement.
66-
/// Use this routine to reset all host parameters to NULL.
67-
///
68-
/// - Throws: ``DatabaseError``
69-
@discardableResult
70-
public func clearBindings() throws -> PreparedStatement {
71-
try check(sqlite3_clear_bindings(stmt))
72-
}
73-
74-
// MARK: - Decodable
75-
76-
public func array<T>(decoding type: T.Type) throws -> [T] where T: Decodable {
77-
var array: [T] = []
78-
while try step() {
79-
let value = try decode(type)
80-
array.append(value)
81-
}
82-
return array
83-
}
84-
85-
public func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
86-
try StatementDecoder().decode(type, from: self)
87-
}
8848
}
8949

9050
// MARK: - Retrieving Statement SQL
@@ -112,16 +72,16 @@ extension PreparedStatement {
11272

11373
extension PreparedStatement {
11474
/// Number of SQL parameters.
115-
public var parameterCount: Int32 { sqlite3_bind_parameter_count(stmt) }
75+
public var parameterCount: Int { Int(sqlite3_bind_parameter_count(stmt)) }
11676

11777
/// Name of a SQL parameter.
118-
public func parameterName(at index: Int32) -> String? {
119-
sqlite3_bind_parameter_name(stmt, index).map { String(cString: $0) }
78+
public func parameterName(at index: Int) -> String? {
79+
sqlite3_bind_parameter_name(stmt, Int32(index)).map { String(cString: $0) }
12080
}
12181

12282
/// Index of a parameter with a given name.
123-
public func parameterIndex(for name: String) -> Int32 {
124-
sqlite3_bind_parameter_index(stmt, name)
83+
public func parameterIndex(for name: String) -> Int {
84+
Int(sqlite3_bind_parameter_index(stmt, name))
12585
}
12686
}
12787

@@ -138,13 +98,14 @@ extension PreparedStatement {
13898
@discardableResult
13999
public func bind(parameters: SQLParameter...) throws -> PreparedStatement {
140100
for (index, parameter) in parameters.enumerated() {
141-
try bind(index: Int32(index + 1), parameter: parameter)
101+
try bind(index: index + 1, parameter: parameter)
142102
}
143103
return self
144104
}
145105

146106
@discardableResult
147-
public func bind(index: Int32, parameter: SQLParameter) throws -> PreparedStatement {
107+
public func bind(index: Int, parameter: SQLParameter) throws -> PreparedStatement {
108+
let index = Int32(index)
148109
let code =
149110
switch parameter {
150111
case .null:
@@ -162,52 +123,118 @@ extension PreparedStatement {
162123
}
163124
return try check(code)
164125
}
126+
127+
/// Reset all bindings on a prepared statement.
128+
///
129+
/// Contrary to the intuition of many, ``PreparedStatement/reset()`` does not reset the bindings on a prepared statement.
130+
/// Use this routine to reset all host parameters to NULL.
131+
///
132+
/// - Throws: ``DatabaseError``
133+
@discardableResult
134+
public func clearBindings() throws -> PreparedStatement {
135+
try check(sqlite3_clear_bindings(stmt))
136+
}
165137
}
166138

167-
// MARK: - Result values from a Query
139+
// MARK: - Columns
168140

169141
extension PreparedStatement {
170142
/// Return the number of columns in the result set.
171-
public var columnCount: Int32 { sqlite3_column_count(stmt) }
143+
public var columnCount: Int { Int(sqlite3_column_count(stmt)) }
172144

173-
public func column(at index: Int32) -> Column {
174-
Column(index: index, statement: self)
145+
/// Returns the name assigned to a specific column in the result set of the SELECT statement.
146+
///
147+
/// The name of a result column is the value of the "AS" clause for that column, if there is an AS clause.
148+
/// If there is no AS clause then the name of the column is unspecified and may change from one release of SQLite to the next.
149+
public func columnName(at index: Int) -> String? {
150+
sqlite3_column_name(stmt, Int32(index)).string
175151
}
152+
}
176153

177-
public func column(for name: String) -> Column? {
178-
columnIndexByName[name].map { Column(index: $0, statement: self) }
154+
// MARK: - Result values from a Query
155+
156+
extension PreparedStatement {
157+
/// The new row of data is ready for processing.
158+
///
159+
/// - Throws: ``DatabaseError``
160+
public func row() throws -> Row? {
161+
switch sqlite3_step(stmt) {
162+
case SQLITE_DONE: nil
163+
case SQLITE_ROW: Row(statement: self)
164+
case let code: throw DatabaseError(code: code, db: db)
165+
}
179166
}
180167

181-
/// Information about a single column of the current result row of a query.
182-
public struct Column {
183-
let index: Int32
168+
public func array<T>(_ type: T.Type) throws -> [T] where T: Decodable {
169+
try array(type, using: RowDecoder.default)
170+
}
171+
172+
public func array<T>(_ type: T.Type, using decoder: RowDecoder) throws -> [T] where T: Decodable {
173+
var array: [T] = []
174+
while let row = try row() {
175+
let value = try row.decode(type, using: decoder)
176+
array.append(value)
177+
}
178+
return array
179+
}
180+
181+
@dynamicMemberLookup
182+
public struct Row {
184183
let statement: PreparedStatement
185-
private var stmt: OpaquePointer { statement.stmt }
186184

187-
/// Returns the name assigned to a specific column in the result set of the SELECT statement.
188-
///
189-
/// The name of a result column is the value of the "AS" clause for that column, if there is an AS clause.
190-
/// If there is no AS clause then the name of the column is unspecified and may change from one release of SQLite to the next.
191-
public var name: String? { sqlite3_column_name(stmt, index).string }
185+
public subscript(dynamicMember name: String) -> Value? {
186+
self[name]
187+
}
188+
189+
public subscript(name: String) -> Value? {
190+
statement.columnIndexByName[name].flatMap { self[$0] }
191+
}
192+
193+
public subscript(index: Int) -> Value? {
194+
if sqlite3_column_type(statement.stmt, Int32(index)) == SQLITE_NULL {
195+
return nil
196+
}
197+
return Value(index: Int32(index), statement: statement)
198+
}
192199

193-
public var isNull: Bool { sqlite3_column_type(stmt, index) == SQLITE_NULL }
200+
public func decode<T>(_ type: T.Type) throws -> T where T: Decodable {
201+
try decode(type, using: RowDecoder.default)
202+
}
203+
204+
public func decode<T>(_ type: T.Type, using decoder: RowDecoder) throws -> T where T: Decodable {
205+
try decoder.decode(type, from: self)
206+
}
207+
}
208+
209+
/// Result value from a query.
210+
public struct Value {
211+
let index: Int32
212+
let statement: PreparedStatement
213+
private var stmt: OpaquePointer { statement.stmt }
194214

195215
/// 64-bit INTEGER result.
196216
public var int64: Int64 { sqlite3_column_int64(stmt, index) }
197217

218+
/// 32-bit INTEGER result.
219+
public var int32: Int32 { sqlite3_column_int(stmt, index) }
220+
221+
/// A platform-specific integer.
222+
public var int: Int { Int(int64) }
223+
198224
/// 64-bit IEEE floating point number.
199225
public var double: Double { sqlite3_column_double(stmt, index) }
200226

227+
/// Size of a BLOB or a UTF-8 TEXT result in bytes.
228+
public var count: Int { Int(sqlite3_column_bytes(stmt, index)) }
229+
201230
/// UTF-8 TEXT result.
202231
public var string: String? {
203232
sqlite3_column_text(stmt, index).flatMap { String(cString: $0) }
204233
}
205234

206235
/// BLOB result.
207236
public var blob: Data? {
208-
sqlite3_column_blob(stmt, index).map { bytes in
209-
Data(bytes: bytes, count: Int(sqlite3_column_bytes(stmt, index)))
210-
}
237+
sqlite3_column_blob(stmt, index).map { Data(bytes: $0, count: count) }
211238
}
212239
}
213240
}

0 commit comments

Comments
 (0)