Skip to content

Commit 7aa1ca4

Browse files
committed
custom aliased encoding
1 parent dac6254 commit 7aa1ca4

File tree

9 files changed

+388
-87
lines changed

9 files changed

+388
-87
lines changed

Sources/Compiler/Gen/Language.swift

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,25 @@ extension Language {
109109
let type = column.value
110110
fields[name] = GeneratedField(
111111
name: name,
112-
type: .builtin(builtinType(for: type), isArray: false),
112+
type: .builtin(
113+
builtinType(for: type),
114+
isArray: false,
115+
encodedAs: builtinForAliasedType(for: type)
116+
),
113117
isArray: type.isRow
114118
)
115119
},
116120
isTable: true
117121
)
118122
}
119123

124+
/// If the column type was aliased then this will return the `builtin`
125+
/// type for the root type of the alias.
126+
private static func builtinForAliasedType(for type: Type) -> String? {
127+
guard case let .alias(root, _) = type else { return nil }
128+
return builtinType(for: root)
129+
}
130+
120131
private static func inputTypeIfNeeded(
121132
statement: Statement,
122133
definition: Definition
@@ -126,7 +137,8 @@ extension Language {
126137
guard statement.parameters.count > 1 else {
127138
return .builtin(
128139
builtinType(for: firstParameter.type),
129-
isArray: firstParameter.type.isRow
140+
isArray: firstParameter.type.isRow,
141+
encodedAs: builtinForAliasedType(for: firstParameter.type)
130142
)
131143
}
132144

@@ -137,7 +149,11 @@ extension Language {
137149
fields: statement.parameters.reduce(into: [:]) { fields, parameter in
138150
fields[parameter.name] = GeneratedField(
139151
name: parameter.name,
140-
type: .builtin(builtinType(for: parameter.type), isArray: false),
152+
type: .builtin(
153+
builtinType(for: parameter.type),
154+
isArray: false,
155+
encodedAs: builtinForAliasedType(for: parameter.type)
156+
),
141157
isArray: parameter.type.isRow
142158
)
143159
},
@@ -168,7 +184,11 @@ extension Language {
168184

169185
// Only one column returned, just use it's type
170186
guard statement.resultColumns.count > 1 else {
171-
return .builtin(builtinType(for: firstColumn), isArray: firstColumn.isRow)
187+
return .builtin(
188+
builtinType(for: firstColumn),
189+
isArray: firstColumn.isRow,
190+
encodedAs: builtinForAliasedType(for: firstColumn)
191+
)
172192
}
173193

174194
let outputTypeName = definition.output?.description ?? "\(definition.name.capitalizedFirst)Output"
@@ -189,7 +209,11 @@ extension Language {
189209
let type = column.value
190210
fields[name] = GeneratedField(
191211
name: name,
192-
type: .builtin(builtinType(for: type), isArray: false),
212+
type: .builtin(
213+
builtinType(for: type),
214+
isArray: false,
215+
encodedAs: builtinForAliasedType(for: type)
216+
),
193217
isArray: type.isRow
194218
)
195219
}
@@ -217,9 +241,21 @@ public struct GeneratedModel {
217241
}
218242

219243
public struct GeneratedField {
244+
/// The column name
220245
let name: String
246+
/// The type of the field.
247+
/// If it is a `model` that means the user selected
248+
/// all columns from a table `foo.*`
221249
let type: BuiltinOrGenerated
250+
/// Whether or not it is an array. Some fields can take a list
251+
/// as an input for a query like `foo IN :bar`
222252
let isArray: Bool
253+
254+
/// The underlying storage type if it is aliased
255+
var encodedAsType: String? {
256+
guard case let .builtin(_, _, encodedAs) = type else { return nil }
257+
return encodedAs
258+
}
223259
}
224260

225261
public struct GeneratedQuery {
@@ -239,12 +275,17 @@ public struct GeneratedResult<Decl> {
239275
}
240276

241277
public enum BuiltinOrGenerated: CustomStringConvertible {
242-
case builtin(String, isArray: Bool)
278+
/// Types can be aliased. So `TEXT AS UUID`. `encodedAs`
279+
/// would be the `TEXT`. It will allow us to tell the
280+
/// `bind` functions to actually encode to the underlying
281+
/// type rather than just having `UUID` always go to `TEXT`
282+
/// when some users may want a `BLOB`.
283+
case builtin(String, isArray: Bool, encodedAs: String?)
243284
case model(GeneratedModel)
244285

245286
public var description: String {
246287
switch self {
247-
case .builtin(let builtin, let isArray):
288+
case .builtin(let builtin, let isArray, _):
248289
isArray ? "[\(builtin)]" : builtin
249290
case .model(let model):
250291
model.name

Sources/Compiler/Gen/SwiftLanguage.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public struct SwiftLanguage: Language {
2121
case "INT": "Int"
2222
case "INTEGER": "Int"
2323
case "TEXT": "String"
24+
case "BLOB": "Data"
2425
default: "Any"
2526
}
2627
case let .optional(ty): "\(builtinType(for: ty))?"
@@ -175,11 +176,11 @@ public struct SwiftLanguage: Language {
175176

176177
if let input = query.input {
177178
switch input {
178-
case let .builtin(_, isArray):
179-
bind(field: nil, isArray: isArray)
179+
case let .builtin(_, isArray, encodedAs):
180+
bind(field: nil, encodeToType: encodedAs, isArray: isArray)
180181
case .model(let model):
181182
for field in model.fields.values {
182-
bind(field: field.name, isArray: field.isArray)
183+
bind(field: field.name, encodeToType: field.encodedAsType, isArray: field.isArray)
183184
}
184185
}
185186
}
@@ -323,8 +324,13 @@ public struct SwiftLanguage: Language {
323324
var index = 0
324325
for field in model.fields.values {
325326
switch field.type {
326-
case .builtin:
327-
"self.\(raw: field.name) = try row.value(at: start + \(raw: index))"
327+
case .builtin(_, _, let encodedAs):
328+
if let encodedAs {
329+
"self.\(raw: field.name) = try \(raw: field.type)(primitive: row.value(at: start + \(raw: index), as: \(raw: encodedAs).self))"
330+
} else {
331+
"self.\(raw: field.name) = try row.value(at: start + \(raw: index))"
332+
}
333+
328334
let _ = index += 1
329335
case .model(let model):
330336
"self.\(raw: field.name) = try \(raw: field.type)(row: row, startingAt: start + \(raw: index))"
@@ -416,6 +422,7 @@ public struct SwiftLanguage: Language {
416422
@CodeBlockItemListBuilder
417423
private static func bind(
418424
field: String?,
425+
encodeToType: String?,
419426
isArray: Bool
420427
) -> CodeBlockItemListSyntax {
421428
let paramName = if let field {
@@ -424,14 +431,20 @@ public struct SwiftLanguage: Language {
424431
"input"
425432
}
426433

434+
let encode = if let encodeToType {
435+
".encodeTo\(encodeToType)()"
436+
} else {
437+
""
438+
}
439+
427440
if isArray {
428441
"""
429442
for element in \(raw: paramName) {
430-
try statement.bind(value: element)
443+
try statement.bind(value: element\(raw: encode))
431444
}
432445
"""
433446
} else {
434-
"try statement.bind(value: \(raw: paramName))"
447+
"try statement.bind(value: \(raw: paramName)\(raw: encode))"
435448
}
436449
}
437450
}

Sources/Feather/ConnectionPool.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ extension ConnectionPool: Connection {
139139
// tx functions consuming. Cause we cannot call `commit` in
140140
// the `do` and on failure call `rollback` since it would
141141
// have been consumed in the `commit`.
142+
//
143+
// Keeping them is consuming is nice since it stops callers
144+
// from calling `commit` manually since its borrowed
142145
let result = Result {
143146
try execute(tx)
144147
}

Sources/Feather/Cursor.swift

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,39 +27,12 @@ public struct Cursor<Element: RowDecodable>: ~Copyable {
2727
}
2828

2929
public struct Row: ~Copyable {
30-
let sqliteStatement: OpaquePointer
30+
@usableFromInline let sqliteStatement: OpaquePointer
3131

32-
public func value<Value: DatabasePrimitive>(at column: Int32) throws(FeatherError) -> Value {
32+
@inlinable public func value<Value: DatabasePrimitive>(
33+
at column: Int32,
34+
as _: Value.Type = Value.self
35+
) throws(FeatherError) -> Value {
3336
return try Value(from: sqliteStatement, at: column)
3437
}
35-
36-
/// Gets an interator to enumerate all of the columns for the `Row`
37-
public func columnIterator() -> ColumnIterator {
38-
return ColumnIterator(sqliteStatement)
39-
}
40-
41-
/// Will iterate over the columns. Returning the next column
42-
/// in the row. Starts at column 0.
43-
public struct ColumnIterator: ~Copyable {
44-
@usableFromInline var sqliteStatement: OpaquePointer
45-
/// The next column index to read. These indices start
46-
/// with 0, unlike bind params starting at 1
47-
@usableFromInline var column: Int32 = 0
48-
@usableFromInline let count: Int32
49-
50-
init(_ sqliteStatement: OpaquePointer) {
51-
self.sqliteStatement = sqliteStatement
52-
self.count = sqlite3_column_count(sqliteStatement)
53-
}
54-
55-
@inlinable public mutating func next<Value: DatabasePrimitive>() throws(FeatherError) -> Value {
56-
guard column < count else {
57-
throw .noMoreColumns
58-
}
59-
60-
let value = try Value(from: sqliteStatement, at: column)
61-
column += 1
62-
return value
63-
}
64-
}
6538
}

Sources/Feather/DatabasePrimitive.swift

Lines changed: 6 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,6 @@ extension Int: DatabasePrimitive {
4545
}
4646
}
4747

48-
extension UInt: DatabasePrimitive {
49-
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
50-
self = try UInt(bitPattern: Int(from: cursor, at: index))
51-
}
52-
53-
@inlinable public func bind(to statement: OpaquePointer, at index: Int32) throws(FeatherError) {
54-
sqlite3_bind_int(statement, index, Int32(bitPattern: UInt32(self)))
55-
}
56-
}
57-
5848
extension Double: DatabasePrimitive {
5949
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
6050
self = sqlite3_column_double(cursor, index)
@@ -65,23 +55,16 @@ extension Double: DatabasePrimitive {
6555
}
6656
}
6757

68-
extension Float: DatabasePrimitive {
69-
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
70-
self = Float(sqlite3_column_double(cursor, index))
71-
}
72-
73-
@inlinable public func bind(to statement: OpaquePointer, at index: Int32) throws(FeatherError) {
74-
sqlite3_bind_double(statement, index, Double(self))
75-
}
76-
}
77-
78-
extension Bool: DatabasePrimitive {
58+
extension Data: DatabasePrimitive {
7959
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
80-
self = sqlite3_column_int64(cursor, index) == 1
60+
let count = Int(sqlite3_column_bytes(cursor, index))
61+
self = Data(bytes: sqlite3_column_blob(cursor, index), count: count)
8162
}
8263

8364
@inlinable public func bind(to statement: OpaquePointer, at index: Int32) throws(FeatherError) {
84-
sqlite3_bind_int(statement, index, self ? 1 : 0)
65+
_ = withUnsafeBytes {
66+
sqlite3_bind_blob(statement, index, $0.baseAddress, CInt($0.count), SQLITE_TRANSIENT)
67+
}
8568
}
8669
}
8770

@@ -102,21 +85,3 @@ extension Optional: DatabasePrimitive where Wrapped: DatabasePrimitive {
10285
}
10386
}
10487
}
105-
106-
extension UUID: DatabasePrimitive {
107-
@inlinable public init(from cursor: OpaquePointer, at index: Int32) throws(FeatherError) {
108-
guard let ptr = sqlite3_column_text(cursor, index) else {
109-
throw .columnIsNil(index)
110-
}
111-
112-
guard let value = UUID(uuidString: String(cString: ptr)) else {
113-
throw .invalidUuidString
114-
}
115-
116-
self = value
117-
}
118-
119-
@inlinable public func bind(to statement: OpaquePointer, at index: Int32) throws(FeatherError) {
120-
try uuidString.bind(to: statement, at: index)
121-
}
122-
}

0 commit comments

Comments
 (0)