Skip to content

Commit 7c49183

Browse files
committed
add the ability to subscript SQLRow by column name
1 parent 1b4daa2 commit 7c49183

File tree

3 files changed

+100
-4
lines changed

3 files changed

+100
-4
lines changed

Sources/SwiftSQL/SQLStatement.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,21 @@ public final class SQLStatement {
242242
public func columnName(at index: Int) -> String {
243243
String(cString: sqlite3_column_name(ref, Int32(index)))
244244
}
245+
246+
// MARK: Indices from Names
247+
248+
/// Returns the index of a column given its name.
249+
public func columnIndex(forName name: String) -> Int? {
250+
return columnIndices[name]
251+
}
252+
private lazy var columnIndices: [String : Int] = {
253+
var indices: [String : Int] = [:]
254+
indices.reserveCapacity(columnCount)
255+
for index in 0..<columnCount {
256+
indices[String(cString: sqlite3_column_name(ref, Int32(index)))] = index
257+
}
258+
return indices
259+
}()
245260

246261
// MARK: Reset
247262

Sources/SwiftSQLExt/SwiftSQLExt.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public extension SQLStatement {
2020
func rows<T: SQLRowDecodable>(_ type: T.Type, count: Int? = nil) throws -> [T] {
2121
var objects = [T]()
2222
let limit = count ?? Int.max
23+
if let count = count {
24+
objects.reserveCapacity(count)
25+
}
2326
while let object = try row(T.self), objects.count < limit {
2427
objects.append(object)
2528
}
@@ -60,6 +63,32 @@ public struct SQLRow {
6063
public subscript<T: SQLDataType>(index: Int) -> T? {
6164
statement.column(at: index)
6265
}
66+
67+
/// Returns a single column (by its name) of the current result row of a query.
68+
///
69+
/// If the SQL statement does not currently point to a valid row, the result is undefined.
70+
/// If the passed columnName doesn't point to a valid column name, a fatal error is raised.
71+
///
72+
/// - parameter columnName: The name of the column.
73+
public subscript<T: SQLDataType>(columnName: String) -> T {
74+
guard let columnIndex = statement.columnIndex(forName: columnName) else {
75+
fatalError("No such column \(columnName)")
76+
}
77+
return statement.column(at: columnIndex)
78+
}
79+
80+
/// Returns a single column (by its name) of the current result row of a query.
81+
///
82+
/// If the SQL statement does not currently point to a valid row, the result is undefined.
83+
/// If the passed columnName doesn't point to a valid column name, nil is returned.
84+
///
85+
/// - parameter columnName: The name of the column.
86+
public subscript<T: SQLDataType>(columnName: String) -> T? {
87+
guard let columnIndex = statement.columnIndex(forName: columnName) else {
88+
return nil
89+
}
90+
return statement.column(at: columnIndex)
91+
}
6392
}
6493

6594
public protocol SQLRowDecodable {

Tests/SwiftSQLExtTests/SwiftSQLExtTests.swift

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ final class SwiftSQLExtTests: XCTestCase {
6969
User(name: "Alice", level: 80)
7070
])
7171
}
72+
73+
func testNamedSubscriptsSuccess() throws {
74+
// GIVEN
75+
try db.populateStore()
76+
77+
// WHEN
78+
let persons = try db
79+
.prepare("SELECT Name FROM Persons ORDER BY Level ASC")
80+
.rows(Person.self, count: 1)
81+
82+
// THEN
83+
XCTAssertEqual(persons, [
84+
Person(name: "Alice", level: nil)
85+
])
86+
}
7287
}
7388

7489
private extension SQLConnection {
@@ -81,19 +96,42 @@ private extension SQLConnection {
8196
Level INTEGER
8297
)
8398
""")
99+
try execute("""
100+
CREATE TABLE Persons
101+
(
102+
Id INTEGER PRIMARY KEY NOT NULL,
103+
Name VARCHAR,
104+
Level INTEGER
105+
)
106+
""")
84107

85-
let statement = try self.prepare("""
108+
let insertUsersStatement = try self.prepare("""
86109
INSERT INTO Users (Name, Level)
87110
VALUES (?, ?)
88111
""")
89112

90-
try statement
113+
try insertUsersStatement
91114
.bind("Alice", Int64(80))
92115
.execute()
93116

94-
try statement.reset()
117+
try insertUsersStatement.reset()
95118

96-
try statement
119+
try insertUsersStatement
120+
.bind("Bob", Int64(90))
121+
.execute()
122+
123+
let insertPersonsStatement = try self.prepare("""
124+
INSERT INTO Persons (Name, Level)
125+
VALUES (?, ?)
126+
""")
127+
128+
try insertPersonsStatement
129+
.bind("Alice", Int64(80))
130+
.execute()
131+
132+
try insertPersonsStatement.reset()
133+
134+
try insertPersonsStatement
97135
.bind("Bob", Int64(90))
98136
.execute()
99137
}
@@ -114,3 +152,17 @@ private struct User: Hashable, SQLRowDecodable {
114152
self.level = row[1]
115153
}
116154
}
155+
private struct Person: Hashable, SQLRowDecodable {
156+
let name: String
157+
let level: Int64?
158+
159+
init(name: String, level: Int64?) {
160+
self.name = name
161+
self.level = level
162+
}
163+
164+
init(row: SQLRow) throws {
165+
self.name = row["Name"]
166+
self.level = row["Level"]
167+
}
168+
}

0 commit comments

Comments
 (0)