Skip to content
Open
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
4 changes: 2 additions & 2 deletions Sources/StructuredQueriesCore/AggregateFunctions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ where QueryValue: _OptionalPromotable, QueryValue._Optionalized.Wrapped: Numeric
public func total(
distinct isDistinct: Bool = false,
filter: (some QueryExpression<Bool>)? = Bool?.none
) -> some QueryExpression<QueryValue> {
AggregateFunctionExpression(
) -> some QueryExpression<Double> {
AggregateFunctionExpression<Double>(
Comment on lines +185 to +186

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caught a small "bug" in the process, where we were allowing total to coalesce to any type even though its strict affinity is REAL.

"total",
isDistinct: isDistinct,
[queryFragment],
Expand Down
4 changes: 4 additions & 0 deletions Sources/StructuredQueriesCore/QueryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,9 @@ extension QueryDecoder {
}

public enum QueryDecodingError: Error {
/// A required column was `NULL`.
case missingRequiredColumn

/// A column's value could not be decoded as the given type.
case typeMismatch(Any.Type)
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension Date.UnixTimeRepresentation: QueryBindable {

extension Date.UnixTimeRepresentation: QueryDecodable {
public init(decoder: inout some QueryDecoder) throws {
try self.init(queryOutput: Date(timeIntervalSince1970: Double(decoder: &decoder)))
try self.init(queryOutput: Date(timeIntervalSince1970: Double(Int64(decoder: &decoder))))
}
}

Expand Down
62 changes: 45 additions & 17 deletions Sources/_StructuredQueriesSQLite/SQLiteFunctionDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,23 @@ struct SQLiteFunctionDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? {
defer { currentIndex += 1 }
precondition(argumentCount > currentIndex)
let value = arguments?[Int(currentIndex)]
guard sqlite3_value_type(value) != SQLITE_NULL else { return nil }
if let blob = sqlite3_value_blob(value) {
let count = Int(sqlite3_value_bytes(value))
let buffer = UnsafeRawBufferPointer(start: blob, count: count)
return [UInt8](buffer)
} else {
return []
switch sqlite3_value_type(value) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_BLOB:
defer { currentIndex += 1 }
if let blob = sqlite3_value_blob(value) {
let count = Int(sqlite3_value_bytes(value))
let buffer = UnsafeRawBufferPointer(start: blob, count: count)
return [UInt8](buffer)
} else {
return []
}
default:
throw QueryDecodingError.typeMismatch([UInt8].self)
}
}

Expand All @@ -57,11 +64,18 @@ struct SQLiteFunctionDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: Double.Type) throws -> Double? {
defer { currentIndex += 1 }
precondition(argumentCount > currentIndex)
let value = arguments?[Int(currentIndex)]
guard sqlite3_value_type(value) != SQLITE_NULL else { return nil }
return sqlite3_value_double(value)
switch sqlite3_value_type(value) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_FLOAT:
defer { currentIndex += 1 }
return sqlite3_value_double(value)
default:
throw QueryDecodingError.typeMismatch(Double.self)
}
}

@inlinable
Expand All @@ -71,20 +85,34 @@ struct SQLiteFunctionDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: Int64.Type) throws -> Int64? {
defer { currentIndex += 1 }
precondition(argumentCount > currentIndex)
let value = arguments?[Int(currentIndex)]
guard sqlite3_value_type(value) != SQLITE_NULL else { return nil }
return sqlite3_value_int64(value)
switch sqlite3_value_type(value) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_INTEGER:
defer { currentIndex += 1 }
return sqlite3_value_int64(value)
default:
throw QueryDecodingError.typeMismatch(Int64.self)
}
}

@inlinable
mutating func decode(_ columnType: String.Type) throws -> String? {
defer { currentIndex += 1 }
precondition(argumentCount > currentIndex)
let value = arguments?[Int(currentIndex)]
guard sqlite3_value_type(value) != SQLITE_NULL else { return nil }
return String(cString: sqlite3_value_text(value))
switch sqlite3_value_type(value) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_TEXT:
defer { currentIndex += 1 }
return String(cString: sqlite3_value_text(value))
default:
throw QueryDecodingError.typeMismatch(String.self)
}
}

@inlinable
Expand Down
60 changes: 44 additions & 16 deletions Sources/_StructuredQueriesSQLite/SQLiteQueryDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,22 @@ struct SQLiteQueryDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: [UInt8].Type) throws -> [UInt8]? {
defer { currentIndex += 1 }
precondition(sqlite3_column_count(statement) > currentIndex)
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
return [UInt8](
UnsafeRawBufferPointer(
start: sqlite3_column_blob(statement, currentIndex),
count: Int(sqlite3_column_bytes(statement, currentIndex))
switch sqlite3_column_type(statement, currentIndex) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_BLOB:
defer { currentIndex += 1 }
return [UInt8](
UnsafeRawBufferPointer(
start: sqlite3_column_blob(statement, currentIndex),
count: Int(sqlite3_column_bytes(statement, currentIndex))
)
)
)
default:
throw QueryDecodingError.typeMismatch([UInt8].self)
}
}

@inlinable
Expand All @@ -51,10 +58,17 @@ struct SQLiteQueryDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: Double.Type) throws -> Double? {
defer { currentIndex += 1 }
precondition(sqlite3_column_count(statement) > currentIndex)
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
return sqlite3_column_double(statement, currentIndex)
switch sqlite3_column_type(statement, currentIndex) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_FLOAT:
defer { currentIndex += 1 }
return sqlite3_column_double(statement, currentIndex)
default:
throw QueryDecodingError.typeMismatch(Double.self)
}
}

@inlinable
Expand All @@ -64,18 +78,32 @@ struct SQLiteQueryDecoder: QueryDecoder {

@inlinable
mutating func decode(_ columnType: Int64.Type) throws -> Int64? {
defer { currentIndex += 1 }
precondition(sqlite3_column_count(statement) > currentIndex)
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
return sqlite3_column_int64(statement, currentIndex)
switch sqlite3_column_type(statement, currentIndex) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_INTEGER:
defer { currentIndex += 1 }
return sqlite3_column_int64(statement, currentIndex)
default:
throw QueryDecodingError.typeMismatch(Int64.self)
}
}

@inlinable
mutating func decode(_ columnType: String.Type) throws -> String? {
defer { currentIndex += 1 }
precondition(sqlite3_column_count(statement) > currentIndex)
guard sqlite3_column_type(statement, currentIndex) != SQLITE_NULL else { return nil }
return String(cString: sqlite3_column_text(statement, currentIndex))
switch sqlite3_column_type(statement, currentIndex) {
case SQLITE_NULL:
currentIndex += 1
return nil
case SQLITE_TEXT:
defer { currentIndex += 1 }
return String(cString: sqlite3_column_text(statement, currentIndex))
default:
throw QueryDecodingError.typeMismatch(String.self)
}
}

@inlinable
Expand Down
6 changes: 3 additions & 3 deletions Tests/StructuredQueriesTests/AggregateFunctionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ extension SnapshotTests {
"""
} results: {
"""
┌────┐
│ 55 │
└────┘
┌──────
│ 55.0
└──────
"""
}
}
Expand Down