Skip to content

Commit 81b97d9

Browse files
authored
Merge pull request #6 from ahmedk92/sqlrow-true-value-semantics-any
SQLRow true value semantics with Any
2 parents 7caf2d5 + e1d010b commit 81b97d9

File tree

4 files changed

+99
-18
lines changed

4 files changed

+99
-18
lines changed

Sources/SwiftSQL/SQLDataType.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ public protocol SQLDataType {
1414
static func sqlColumn(statement: OpaquePointer, index: Int32) -> Self
1515
}
1616

17+
public extension SQLDataType {
18+
static func convert(from value: Any) -> Self? { value as? Self }
19+
}
20+
1721
extension Int: SQLDataType {
1822
public func sqlBind(statement: OpaquePointer, index: Int32) {
1923
sqlite3_bind_int64(statement, index, Int64(self))

Sources/SwiftSQL/SQLStatement.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,28 @@ public final class SQLStatement {
223223
return T.sqlColumn(statement: ref, index: Int32(index))
224224
}
225225
}
226+
227+
public func column(at index: Int) -> Any? {
228+
let index = Int32(index)
229+
let type = sqlite3_column_type(ref, index)
230+
switch type {
231+
case SQLITE_INTEGER:
232+
return sqlite3_column_int64(ref, index)
233+
case SQLITE_FLOAT:
234+
return sqlite3_column_double(ref, index)
235+
case SQLITE_TEXT:
236+
return String(cString: sqlite3_column_text(ref, index))
237+
case SQLITE_BLOB:
238+
if let bytes = sqlite3_column_blob(ref, index) {
239+
let byteCount = sqlite3_column_bytes(ref, index)
240+
return Data(bytes: bytes, count: Int(byteCount))
241+
} else {
242+
return Data()
243+
}
244+
default:
245+
return nil
246+
}
247+
}
226248

227249
/// Return the number of columns in the result set returned by the statement.
228250
///

Sources/SwiftSQLExt/SwiftSQLExt.swift

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,40 @@ public extension SQLStatement {
2828
}
2929
return objects
3030
}
31+
32+
/// Fetches the next row as `SQLRow`.
33+
func row() throws -> SQLRow? {
34+
guard try step() else {
35+
return nil
36+
}
37+
return SQLRow(statement: self)
38+
}
39+
40+
/// Fetches the first `count` rows as `SQLRow`s returned by the statement. By default,
41+
/// fetches all rows.
42+
func rows(count: Int? = nil) throws -> [SQLRow] {
43+
var objects = [SQLRow]()
44+
let limit = count ?? Int.max
45+
if let count = count {
46+
objects.reserveCapacity(count)
47+
}
48+
while let object = try row(), objects.count < limit {
49+
objects.append(object)
50+
}
51+
return objects
52+
}
3153
}
3254

3355
/// Represents a single row returned by the SQL statement.
34-
///
35-
/// - warning: This is a leaky abstraction. This is not a real value type, it
36-
/// just wraps the underlying statement. If the statement moves to the next
37-
/// row by calling `step()`, the row is also going to point to the new row.
3856
public struct SQLRow {
39-
/// The underlying statement.
40-
public let statement: SQLStatement // Storing as strong reference doesn't seem to affect performance
4157

4258
public init(statement: SQLStatement) {
43-
self.statement = statement
59+
values = (0..<statement.columnCount).map { index in
60+
statement.column(at: index)
61+
}
62+
columnIndicesByNames = Dictionary(uniqueKeysWithValues: (0..<statement.columnCount).map { index in
63+
(statement.columnName(at: index), index)
64+
})
4465
}
4566

4667
/// Returns a single column of the current result row of a query.
@@ -50,7 +71,7 @@ public struct SQLRow {
5071
///
5172
/// - parameter index: The leftmost column of the result set has the index 0.
5273
public subscript<T: SQLDataType>(index: Int) -> T {
53-
statement.column(at: index)
74+
T.convert(from: values[index]!)!
5475
}
5576

5677
/// Returns a single column of the current result row of a query. If the
@@ -61,7 +82,8 @@ public struct SQLRow {
6182
///
6283
/// - parameter index: The leftmost column of the result set has the index 0.
6384
public subscript<T: SQLDataType>(index: Int) -> T? {
64-
statement.column(at: index)
85+
guard let value = values[index] else { return nil }
86+
return T.convert(from: value)
6587
}
6688

6789
/// Returns a single column (by its name) of the current result row of a query.
@@ -71,10 +93,10 @@ public struct SQLRow {
7193
///
7294
/// - parameter columnName: The name of the column.
7395
public subscript<T: SQLDataType>(columnName: String) -> T {
74-
guard let columnIndex = statement.columnIndex(forName: columnName) else {
96+
guard let columnIndex = columnIndicesByNames[columnName] else {
7597
fatalError("No such column \(columnName)")
7698
}
77-
return statement.column(at: columnIndex)
99+
return self[columnIndex]
78100
}
79101

80102
/// Returns a single column (by its name) of the current result row of a query.
@@ -84,11 +106,14 @@ public struct SQLRow {
84106
///
85107
/// - parameter columnName: The name of the column.
86108
public subscript<T: SQLDataType>(columnName: String) -> T? {
87-
guard let columnIndex = statement.columnIndex(forName: columnName) else {
109+
guard let columnIndex = columnIndicesByNames[columnName] else {
88110
return nil
89111
}
90-
return statement.column(at: columnIndex)
112+
return self[columnIndex]
91113
}
114+
115+
private let values: [Any?]
116+
private let columnIndicesByNames: [String : Int]
92117
}
93118

94119
public protocol SQLRowDecodable {

Tests/SwiftSQLExtTests/SwiftSQLExtTests.swift

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,50 @@ final class SwiftSQLExtTests: XCTestCase {
7070
])
7171
}
7272

73-
func testNamedSubscriptsSuccess() throws {
73+
func testIndependentSQLRows() throws {
7474
// GIVEN
7575
try db.populateStore()
7676

7777
// WHEN
78-
let persons = try db
78+
let names: [String] = try db
7979
.prepare("SELECT Name FROM Persons ORDER BY Level ASC")
80-
.rows(Person.self, count: 1)
80+
.rows()
81+
.map({ $0["Name"] })
8182

8283
// THEN
83-
XCTAssertEqual(persons, [
84-
Person(name: "Alice", level: nil)
84+
XCTAssertEqual(names, [
85+
"Alice",
86+
"Bob"
8587
])
8688
}
89+
90+
func testIndependentSingleSQLRowNonNil() throws {
91+
// GIVEN
92+
try db.populateStore()
93+
94+
// WHEN
95+
let row = try db
96+
.prepare("SELECT Name FROM Persons ORDER BY Level ASC")
97+
.row()
98+
99+
// THEN
100+
XCTAssertEqual(try XCTUnwrap(row)["Name"] as String, "Alice")
101+
}
102+
103+
func testIndependentSingleSQLRowNil() throws {
104+
// GIVEN
105+
try db.populateStore()
106+
107+
// WHEN
108+
let row = try db
109+
.prepare("SELECT Name FROM Persons ORDER BY Level ASC")
110+
.row()
111+
112+
// THEN
113+
XCTAssertNil(try XCTUnwrap(row)["Level"] as Int?)
114+
}
115+
116+
87117
}
88118

89119
private extension SQLConnection {

0 commit comments

Comments
 (0)