Skip to content

Commit beab6fc

Browse files
authored
Add explicit Bool/UInt64 cases to QueryBinding (pointfreeco#164)
* Add explicit `Bool` case to `QueryBinding` * wip * wip * wip
1 parent 0bd2c9b commit beab6fc

File tree

9 files changed

+84
-39
lines changed

9 files changed

+84
-39
lines changed

Sources/StructuredQueriesCore/QueryBindable.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,10 @@ extension UInt32: QueryBindable {
132132

133133
extension UInt64: QueryBindable {
134134
public var queryBinding: QueryBinding {
135-
if self > UInt64(Int64.max) {
136-
return .invalid(OverflowError())
137-
} else {
138-
return .int(Int64(self))
139-
}
135+
return .uint(self)
140136
}
141137
public init?(queryBinding: QueryBinding) {
142-
guard case .int(let value) = queryBinding, value >= UInt64.min else { return nil }
138+
guard case .uint(let value) = queryBinding else { return nil }
143139
self.init(value)
144140
}
145141
}

Sources/StructuredQueriesCore/QueryBinding.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ public enum QueryBinding: Hashable, Sendable {
55
/// A value that should be bound to a statement as bytes.
66
case blob([UInt8])
77

8+
/// A value that should be bound to a statement as a Boolean.
9+
case bool(Bool)
10+
811
/// A value that should be bound to a statement as a double.
912
case double(Double)
1013

@@ -20,6 +23,9 @@ public enum QueryBinding: Hashable, Sendable {
2023
/// A value that should be bound to a statement as a string.
2124
case text(String)
2225

26+
/// A value that should be bound to a statement as an unsigned integer.
27+
case uint(UInt64)
28+
2329
/// A value that should be bound to a statement as a unique identifier.
2430
case uuid(UUID)
2531

@@ -45,22 +51,26 @@ public struct QueryBindingError: Error, Hashable {
4551
extension QueryBinding: CustomDebugStringConvertible {
4652
public var debugDescription: String {
4753
switch self {
48-
case .blob(let data):
49-
return String(decoding: data, as: UTF8.self)
54+
case .blob(let blob):
55+
return String(decoding: blob, as: UTF8.self)
5056
.debugDescription
5157
.dropLast()
5258
.dropFirst()
5359
.quoted(.text)
60+
case .bool(let bool):
61+
return bool ? "1" : "0"
5462
case .date(let date):
5563
return date.iso8601String.quoted(.text)
56-
case .double(let value):
57-
return "\(value)"
58-
case .int(let value):
59-
return "\(value)"
64+
case .double(let double):
65+
return "\(double)"
66+
case .int(let int):
67+
return "\(int)"
6068
case .null:
6169
return "NULL"
62-
case .text(let string):
63-
return string.quoted(.text)
70+
case .text(let text):
71+
return text.quoted(.text)
72+
case .uint(let uint):
73+
return "\(uint)"
6474
case .uuid(let uuid):
6575
return uuid.uuidString.lowercased().quoted(.text)
6676
case .invalid(let error):

Sources/StructuredQueriesCore/QueryDecodable.swift

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -109,43 +109,43 @@ extension Int32: QueryDecodable {
109109
extension UInt: QueryDecodable {
110110
@inlinable
111111
public init(decoder: inout some QueryDecoder) throws {
112-
let n = try Int64(decoder: &decoder)
113-
guard n >= 0 else { throw OverflowError() }
114-
self.init(n)
112+
try self.init(UInt64(decoder: &decoder))
115113
}
116114
}
117115

118116
extension UInt8: QueryDecodable {
119117
@inlinable
120118
public init(decoder: inout some QueryDecoder) throws {
121-
let n = try Int64(decoder: &decoder)
122-
guard (Int64(UInt8.min)...Int64(UInt8.max)).contains(n) else { throw OverflowError() }
119+
let n = try UInt64(decoder: &decoder)
120+
guard (UInt64(UInt8.min)...UInt64(UInt8.max)).contains(n) else { throw OverflowError() }
123121
self.init(n)
124122
}
125123
}
126124

127125
extension UInt16: QueryDecodable {
128126
@inlinable
129127
public init(decoder: inout some QueryDecoder) throws {
130-
let n = try Int64(decoder: &decoder)
131-
guard (Int64(UInt16.min)...Int64(UInt16.max)).contains(n) else { throw OverflowError() }
128+
let n = try UInt64(decoder: &decoder)
129+
guard (UInt64(UInt16.min)...UInt64(UInt16.max)).contains(n) else { throw OverflowError() }
132130
self.init(n)
133131
}
134132
}
135133

136134
extension UInt32: QueryDecodable {
137135
@inlinable
138136
public init(decoder: inout some QueryDecoder) throws {
139-
let n = try Int64(decoder: &decoder)
140-
guard (Int64(UInt32.min)...Int64(UInt32.max)).contains(n) else { throw OverflowError() }
137+
let n = try UInt64(decoder: &decoder)
138+
guard (UInt64(UInt32.min)...UInt64(UInt32.max)).contains(n) else { throw OverflowError() }
141139
self.init(n)
142140
}
143141
}
144142

145143
extension UInt64: QueryDecodable {
146144
@inlinable
147145
public init(decoder: inout some QueryDecoder) throws {
148-
try self.init(Int64(decoder: &decoder))
146+
guard let result = try decoder.decode(UInt64.self)
147+
else { throw QueryDecodingError.missingRequiredColumn }
148+
self = result
149149
}
150150
}
151151

Sources/StructuredQueriesCore/QueryDecoder.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public protocol QueryDecoder {
2020
/// - Returns: A value of the requested type, or `nil` if the column is `NULL`.
2121
mutating func decode(_ columnType: Int64.Type) throws -> Int64?
2222

23+
/// Decodes a single value of the given type from the current column.
24+
///
25+
/// - Parameter columnType: The type to decode as.
26+
/// - Returns: A value of the requested type, or `nil` if the column is `NULL`.
27+
mutating func decode(_ columnType: UInt64.Type) throws -> UInt64?
28+
2329
/// Decodes a single value of the given type from the current column.
2430
///
2531
/// - Parameter columnType: The type to decode as.

Sources/StructuredQueriesSQLiteCore/Triggers.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,8 @@ public struct TemporaryTrigger<On: Table>: Sendable, Statement {
442442
$0.append(hex)
443443
}
444444
$0.append("unhex(\(quote: hex, delimiter: .text))")
445+
case .bool(let bool):
446+
$0.append("\(raw: bool ? 1 : 0)")
445447
case .double(let double):
446448
$0.append("\(raw: double)")
447449
case .date(let date):
@@ -464,6 +466,8 @@ public struct TemporaryTrigger<On: Table>: Sendable, Statement {
464466
$0.append("NULL")
465467
case .text(let string):
466468
$0.append("\(quote: string, delimiter: .text)")
469+
case .uint(let uint):
470+
$0.append("\(raw: uint)")
467471
case .uuid(let uuid):
468472
reportIssue(
469473
"""

Sources/_StructuredQueriesSQLite/Database.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ public struct Database {
140140
switch binding {
141141
case .blob(let blob):
142142
sqlite3_bind_blob(statement, index, Array(blob), Int32(blob.count), SQLITE_TRANSIENT)
143+
case .bool(let bool):
144+
sqlite3_bind_int64(statement, index, bool ? 1 : 0)
143145
case .date(let date):
144146
sqlite3_bind_text(statement, index, date.iso8601String, -1, SQLITE_TRANSIENT)
145147
case .double(let double):
@@ -150,6 +152,10 @@ public struct Database {
150152
sqlite3_bind_null(statement, index)
151153
case .text(let text):
152154
sqlite3_bind_text(statement, index, text, -1, SQLITE_TRANSIENT)
155+
case .uint(let uint) where uint <= UInt64(Int64.max):
156+
sqlite3_bind_int64(statement, index, Int64(uint))
157+
case .uint(let uint):
158+
throw Int64OverflowError(unsignedInteger: uint)
153159
case .uuid(let uuid):
154160
sqlite3_bind_text(statement, index, uuid.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
155161
case .invalid(let error):
@@ -190,7 +196,7 @@ public struct Database {
190196
}
191197
}
192198

193-
private struct InvalidBindingError: Error {}
199+
struct Int64OverflowError: Error { let unsignedInteger: UInt64 }
194200

195201
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
196202

Sources/_StructuredQueriesSQLite/DatabaseFunction.swift

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,26 @@ extension [QueryBinding] {
6767
extension QueryBinding {
6868
fileprivate func result(db: OpaquePointer?) {
6969
switch self {
70-
case .blob(let value):
71-
sqlite3_result_blob(db, Array(value), Int32(value.count), SQLITE_TRANSIENT)
72-
case .double(let value):
73-
sqlite3_result_double(db, value)
74-
case .date(let value):
75-
sqlite3_result_text(db, value.iso8601String, -1, SQLITE_TRANSIENT)
76-
case .int(let value):
77-
sqlite3_result_int64(db, value)
70+
case .blob(let blob):
71+
sqlite3_result_blob(db, Array(blob), Int32(blob.count), SQLITE_TRANSIENT)
72+
case .bool(let bool):
73+
sqlite3_result_int64(db, bool ? 1 : 0)
74+
case .double(let double):
75+
sqlite3_result_double(db, double)
76+
case .date(let date):
77+
sqlite3_result_text(db, date.iso8601String, -1, SQLITE_TRANSIENT)
78+
case .int(let int):
79+
sqlite3_result_int64(db, int)
7880
case .null:
7981
sqlite3_result_null(db)
80-
case .text(let value):
81-
sqlite3_result_text(db, value, -1, SQLITE_TRANSIENT)
82-
case .uuid(let value):
83-
sqlite3_result_text(db, value.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
82+
case .text(let text):
83+
sqlite3_result_text(db, text, -1, SQLITE_TRANSIENT)
84+
case .uint(let uint) where uint <= UInt64(Int64.max):
85+
sqlite3_result_int64(db, Int64(uint))
86+
case .uint(let uint):
87+
sqlite3_result_error(db, "Unsigned integer \(uint) overflows Int64.max", -1)
88+
case .uuid(let uuid):
89+
sqlite3_result_text(db, uuid.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
8490
case .invalid(let error):
8591
sqlite3_result_error(db, error.underlyingError.localizedDescription, -1)
8692
}

Sources/_StructuredQueriesSQLite/SQLiteQueryDecoder.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,26 @@ struct SQLiteQueryDecoder: QueryDecoder {
7171
return String(cString: sqlite3_column_text(statement, currentIndex))
7272
}
7373

74+
@inlinable
75+
mutating func decode(_ columnType: UInt64.Type) throws -> UInt64? {
76+
guard let n = try decode(Int64.self) else { return nil }
77+
guard n >= 0 else { throw UInt64OverflowError(signedInteger: n) }
78+
return UInt64(n)
79+
}
80+
7481
@usableFromInline
7582
mutating func decode(_ columnType: UUID.Type) throws -> UUID? {
7683
guard let uuidString = try decode(String.self) else { return nil }
7784
return UUID(uuidString: uuidString)
7885
}
7986
}
87+
88+
@usableFromInline
89+
struct UInt64OverflowError: Error {
90+
let signedInteger: Int64
91+
92+
@usableFromInline
93+
init(signedInteger: Int64) {
94+
self.signedInteger = signedInteger
95+
}
96+
}

Tests/StructuredQueriesTests/BindingTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ extension SnapshotTests {
6161
INSERT INTO "records"
6262
("id", "name", "duration")
6363
VALUES
64-
('\u{07AD}��ޭ��ޭ��ޭ��', '', <invalid: The operation couldn’t be completed. (StructuredQueriesCore.OverflowError error 1.)>)
64+
('\u{07AD}��ޭ��ޭ��ޭ��', '', 18446744073709551615)
6565
RETURNING "id", "name", "duration"
6666
"""#
6767
} results: {
6868
"""
69-
The operation couldn’t be completed. (StructuredQueriesCore.OverflowError error 1.)
69+
The operation couldn’t be completed. (_StructuredQueriesSQLite.Int64OverflowError error 1.)
7070
"""
7171
}
7272
}

0 commit comments

Comments
 (0)