Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions Sources/StructuredQueriesCore/QueryBindable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,10 @@ extension UInt32: QueryBindable {

extension UInt64: QueryBindable {
public var queryBinding: QueryBinding {
if self > UInt64(Int64.max) {
return .invalid(OverflowError())
} else {
return .int(Int64(self))
}
return .uint(self)
}
public init?(queryBinding: QueryBinding) {
guard case .int(let value) = queryBinding, value >= UInt64.min else { return nil }
guard case .uint(let value) = queryBinding else { return nil }
self.init(value)
}
}
Expand Down
26 changes: 18 additions & 8 deletions Sources/StructuredQueriesCore/QueryBinding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ public enum QueryBinding: Hashable, Sendable {
/// A value that should be bound to a statement as bytes.
case blob([UInt8])

/// A value that should be bound to a statement as a Boolean.
case bool(Bool)

/// A value that should be bound to a statement as a double.
case double(Double)

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

/// A value that should be bound to a statement as an unsigned integer.
case uint(UInt64)

/// A value that should be bound to a statement as a unique identifier.
case uuid(UUID)

Expand All @@ -45,22 +51,26 @@ public struct QueryBindingError: Error, Hashable {
extension QueryBinding: CustomDebugStringConvertible {
public var debugDescription: String {
switch self {
case .blob(let data):
return String(decoding: data, as: UTF8.self)
case .blob(let blob):
return String(decoding: blob, as: UTF8.self)
.debugDescription
.dropLast()
.dropFirst()
.quoted(.text)
case .bool(let bool):
return bool ? "1" : "0"
case .date(let date):
return date.iso8601String.quoted(.text)
case .double(let value):
return "\(value)"
case .int(let value):
return "\(value)"
case .double(let double):
return "\(double)"
case .int(let int):
return "\(int)"
case .null:
return "NULL"
case .text(let string):
return string.quoted(.text)
case .text(let text):
return text.quoted(.text)
case .uint(let uint):
return "\(uint)"
case .uuid(let uuid):
return uuid.uuidString.lowercased().quoted(.text)
case .invalid(let error):
Expand Down
20 changes: 10 additions & 10 deletions Sources/StructuredQueriesCore/QueryDecodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,43 +109,43 @@ extension Int32: QueryDecodable {
extension UInt: QueryDecodable {
@inlinable
public init(decoder: inout some QueryDecoder) throws {
let n = try Int64(decoder: &decoder)
guard n >= 0 else { throw OverflowError() }
self.init(n)
try self.init(UInt64(decoder: &decoder))
}
}

extension UInt8: QueryDecodable {
@inlinable
public init(decoder: inout some QueryDecoder) throws {
let n = try Int64(decoder: &decoder)
guard (Int64(UInt8.min)...Int64(UInt8.max)).contains(n) else { throw OverflowError() }
let n = try UInt64(decoder: &decoder)
guard (UInt64(UInt8.min)...UInt64(UInt8.max)).contains(n) else { throw OverflowError() }
self.init(n)
}
}

extension UInt16: QueryDecodable {
@inlinable
public init(decoder: inout some QueryDecoder) throws {
let n = try Int64(decoder: &decoder)
guard (Int64(UInt16.min)...Int64(UInt16.max)).contains(n) else { throw OverflowError() }
let n = try UInt64(decoder: &decoder)
guard (UInt64(UInt16.min)...UInt64(UInt16.max)).contains(n) else { throw OverflowError() }
self.init(n)
}
}

extension UInt32: QueryDecodable {
@inlinable
public init(decoder: inout some QueryDecoder) throws {
let n = try Int64(decoder: &decoder)
guard (Int64(UInt32.min)...Int64(UInt32.max)).contains(n) else { throw OverflowError() }
let n = try UInt64(decoder: &decoder)
guard (UInt64(UInt32.min)...UInt64(UInt32.max)).contains(n) else { throw OverflowError() }
self.init(n)
}
}

extension UInt64: QueryDecodable {
@inlinable
public init(decoder: inout some QueryDecoder) throws {
try self.init(Int64(decoder: &decoder))
guard let result = try decoder.decode(UInt64.self)
else { throw QueryDecodingError.missingRequiredColumn }
self = result
}
}

Expand Down
6 changes: 6 additions & 0 deletions Sources/StructuredQueriesCore/QueryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ public protocol QueryDecoder {
/// - Returns: A value of the requested type, or `nil` if the column is `NULL`.
mutating func decode(_ columnType: Int64.Type) throws -> Int64?

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

/// Decodes a single value of the given type from the current column.
///
/// - Parameter columnType: The type to decode as.
Expand Down
4 changes: 4 additions & 0 deletions Sources/StructuredQueriesSQLiteCore/Triggers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ public struct TemporaryTrigger<On: Table>: Sendable, Statement {
$0.append(hex)
}
$0.append("unhex(\(quote: hex, delimiter: .text))")
case .bool(let bool):
$0.append("\(raw: bool ? 1 : 0)")
case .double(let double):
$0.append("\(raw: double)")
case .date(let date):
Expand All @@ -464,6 +466,8 @@ public struct TemporaryTrigger<On: Table>: Sendable, Statement {
$0.append("NULL")
case .text(let string):
$0.append("\(quote: string, delimiter: .text)")
case .uint(let uint):
$0.append("\(raw: uint)")
case .uuid(let uuid):
reportIssue(
"""
Expand Down
8 changes: 7 additions & 1 deletion Sources/_StructuredQueriesSQLite/Database.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public struct Database {
switch binding {
case .blob(let blob):
sqlite3_bind_blob(statement, index, Array(blob), Int32(blob.count), SQLITE_TRANSIENT)
case .bool(let bool):
sqlite3_bind_int64(statement, index, bool ? 1 : 0)
case .date(let date):
sqlite3_bind_text(statement, index, date.iso8601String, -1, SQLITE_TRANSIENT)
case .double(let double):
Expand All @@ -150,6 +152,10 @@ public struct Database {
sqlite3_bind_null(statement, index)
case .text(let text):
sqlite3_bind_text(statement, index, text, -1, SQLITE_TRANSIENT)
case .uint(let uint) where uint <= UInt64(Int64.max):
sqlite3_bind_int64(statement, index, Int64(uint))
case .uint(let uint):
throw Int64OverflowError(unsignedInteger: uint)
case .uuid(let uuid):
sqlite3_bind_text(statement, index, uuid.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
case .invalid(let error):
Expand Down Expand Up @@ -190,7 +196,7 @@ public struct Database {
}
}

private struct InvalidBindingError: Error {}
struct Int64OverflowError: Error { let unsignedInteger: UInt64 }

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

Expand Down
30 changes: 18 additions & 12 deletions Sources/_StructuredQueriesSQLite/DatabaseFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,26 @@ extension [QueryBinding] {
extension QueryBinding {
fileprivate func result(db: OpaquePointer?) {
switch self {
case .blob(let value):
sqlite3_result_blob(db, Array(value), Int32(value.count), SQLITE_TRANSIENT)
case .double(let value):
sqlite3_result_double(db, value)
case .date(let value):
sqlite3_result_text(db, value.iso8601String, -1, SQLITE_TRANSIENT)
case .int(let value):
sqlite3_result_int64(db, value)
case .blob(let blob):
sqlite3_result_blob(db, Array(blob), Int32(blob.count), SQLITE_TRANSIENT)
case .bool(let bool):
sqlite3_result_int64(db, bool ? 1 : 0)
case .double(let double):
sqlite3_result_double(db, double)
case .date(let date):
sqlite3_result_text(db, date.iso8601String, -1, SQLITE_TRANSIENT)
case .int(let int):
sqlite3_result_int64(db, int)
case .null:
sqlite3_result_null(db)
case .text(let value):
sqlite3_result_text(db, value, -1, SQLITE_TRANSIENT)
case .uuid(let value):
sqlite3_result_text(db, value.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
case .text(let text):
sqlite3_result_text(db, text, -1, SQLITE_TRANSIENT)
case .uint(let uint) where uint <= UInt64(Int64.max):
sqlite3_result_int64(db, Int64(uint))
case .uint(let uint):
sqlite3_result_error(db, "Unsigned integer \(uint) overflows Int64.max", -1)
case .uuid(let uuid):
sqlite3_result_text(db, uuid.uuidString.lowercased(), -1, SQLITE_TRANSIENT)
case .invalid(let error):
sqlite3_result_error(db, error.underlyingError.localizedDescription, -1)
}
Expand Down
17 changes: 17 additions & 0 deletions Sources/_StructuredQueriesSQLite/SQLiteQueryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,26 @@ struct SQLiteQueryDecoder: QueryDecoder {
return String(cString: sqlite3_column_text(statement, currentIndex))
}

@inlinable
mutating func decode(_ columnType: UInt64.Type) throws -> UInt64? {
guard let n = try decode(Int64.self) else { return nil }
guard n >= 0 else { throw UInt64OverflowError(signedInteger: n) }
return UInt64(n)
}

@usableFromInline
mutating func decode(_ columnType: UUID.Type) throws -> UUID? {
guard let uuidString = try decode(String.self) else { return nil }
return UUID(uuidString: uuidString)
}
}

@usableFromInline
struct UInt64OverflowError: Error {
let signedInteger: Int64

@usableFromInline
init(signedInteger: Int64) {
self.signedInteger = signedInteger
}
}
4 changes: 2 additions & 2 deletions Tests/StructuredQueriesTests/BindingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@ extension SnapshotTests {
INSERT INTO "records"
("id", "name", "duration")
VALUES
('\u{07AD}��ޭ��ޭ��ޭ��', '', <invalid: The operation couldn’t be completed. (StructuredQueriesCore.OverflowError error 1.)>)
('\u{07AD}��ޭ��ޭ��ޭ��', '', 18446744073709551615)
RETURNING "id", "name", "duration"
"""#
} results: {
"""
The operation couldn’t be completed. (StructuredQueriesCore.OverflowError error 1.)
The operation couldn’t be completed. (_StructuredQueriesSQLite.Int64OverflowError error 1.)
"""
}
}
Expand Down