should be it

This commit is contained in:
2025-10-24 19:21:19 -05:00
parent a4b23fc57c
commit f09560c7b1
14047 changed files with 3161551 additions and 1 deletions

View File

@@ -0,0 +1,297 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
@_implementationOnly import Cduckdb
import Foundation
// MARK: - Type Layouts
struct duckdb_list_entry_t {
let offset: UInt64
let length: UInt64
}
extension duckdb_state {
static let success = duckdb_state(0)
static let failure = duckdb_state(1)
}
extension duckdb_pending_state {
static let ready = duckdb_pending_state(0)
static let notReady = duckdb_pending_state(1)
static let error = duckdb_pending_state(2)
}
// MARK: - Type ID
extension duckdb_type {
var asTypeID: DatabaseType { .init(rawValue: rawValue) }
}
// MARK: - String
extension duckdb_string {
private static let inlineLimit = UInt32(12)
var asString: String {
withUnsafePointer(to: self) { ptr in
let contentsSize = UnsafeRawPointer(ptr).load(as: UInt32.self)
let strPtr: UnsafeRawPointer
if contentsSize <= Self.inlineLimit {
strPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<UInt32>.stride)
}
else {
let strPtrPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<UInt64>.stride)
strPtr = strPtrPtr.load(as: UnsafeRawPointer.self)
}
let stringData = Data(bytes: strPtr, count: Int(contentsSize))
return String(data: stringData, encoding:.utf8)!
}
}
}
// MARK: - Huge Int
extension duckdb_hugeint {
init(_ source: IntHuge) {
self = duckdb_hugeint(lower: source.low, upper: source.high)
}
var asIntHuge: IntHuge { .init(high: upper, low: lower) }
var asUIntHuge: UIntHuge {
let high = IntHuge(upper) + IntHuge(Int64.max) + 1
return UIntHuge(high) << 64 + UIntHuge(lower)
}
var asUUID: UUID {
let value = asUIntHuge
let uuid = UUID(
uuid: uuid_t(
UInt8(truncatingIfNeeded: value >> (8 * 15)),
UInt8(truncatingIfNeeded: value >> (8 * 14)),
UInt8(truncatingIfNeeded: value >> (8 * 13)),
UInt8(truncatingIfNeeded: value >> (8 * 12)),
UInt8(truncatingIfNeeded: value >> (8 * 11)),
UInt8(truncatingIfNeeded: value >> (8 * 10)),
UInt8(truncatingIfNeeded: value >> (8 * 9)),
UInt8(truncatingIfNeeded: value >> (8 * 8)),
UInt8(truncatingIfNeeded: value >> (8 * 7)),
UInt8(truncatingIfNeeded: value >> (8 * 6)),
UInt8(truncatingIfNeeded: value >> (8 * 5)),
UInt8(truncatingIfNeeded: value >> (8 * 4)),
UInt8(truncatingIfNeeded: value >> (8 * 3)),
UInt8(truncatingIfNeeded: value >> (8 * 2)),
UInt8(truncatingIfNeeded: value >> (8 * 1)),
UInt8(truncatingIfNeeded: value >> (8 * 0))
)
)
return uuid
}
}
// MARK: - Unsigned Huge Int
extension duckdb_uhugeint {
init(_ source: UIntHuge) {
self = duckdb_uhugeint(lower: source.low, upper: source.high)
}
var asUIntHuge: UIntHuge { .init(high: upper, low: lower) }
}
// MARK: - Time
extension duckdb_time {
init(time: Time) { self = duckdb_time(micros: time.microseconds) }
var asTime: Time { Time(microseconds: micros) }
}
extension duckdb_time_struct {
init(components: Time.Components) {
self = duckdb_time_struct(
hour: components.hour,
min: components.minute,
sec: components.second,
micros: components.microsecond
)
}
var asTimeComponents: Time.Components {
.init(hour: hour, minute: min, second: sec, microsecond: micros)
}
}
extension duckdb_time_tz {
var asTime: TimeTz {
let res = duckdb_from_time_tz(self)
let ctimestruct = duckdb_to_time(res.time);
return TimeTz(time: Time(microseconds: ctimestruct.micros), offset: res.offset)
}
}
// MARK: - Date
extension duckdb_date {
init(date: Date) { self = duckdb_date(days: date.days) }
var asDate: Date { Date(days: days) }
}
extension duckdb_date_struct {
init(components: Date.Components) {
self = duckdb_date_struct(year: components.year, month: components.month, day: components.day)
}
var asDateComponents: Date.Components {
.init(year: year, month: month, day: day)
}
}
// MARK: - Timestamp
extension duckdb_timestamp {
init(timestamp: Timestamp) { self = duckdb_timestamp(micros: timestamp.microseconds) }
var asTimestamp: Timestamp { Timestamp(microseconds: micros) }
}
extension duckdb_timestamp_struct {
init(components: Timestamp.Components) {
self = duckdb_timestamp_struct(
date: duckdb_date_struct(components: components.date),
time: duckdb_time_struct(components: components.time)
)
}
var asTimestampComponents: Timestamp.Components {
.init(date: date.asDateComponents, time: time.asTimeComponents)
}
}
// MARK: - Interval
extension duckdb_interval {
init(interval: Interval) {
self = duckdb_interval(
months: interval.months, days: interval.days, micros: interval.microseconds)
}
var asInterval: Interval {
Interval(months: months, days: days, microseconds: micros)
}
}
// MARK: - Blob
extension duckdb_blob {
private static let inlineLimit = UInt32(12)
var asData: Data {
withUnsafePointer(to: self) { ptr in
let contentsSize = UnsafeRawPointer(ptr).load(as: UInt32.self)
let blobPtr: UnsafeRawPointer
if contentsSize <= Self.inlineLimit {
blobPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<UInt32>.stride)
}
else {
let blobPtrPtr = UnsafeRawPointer(ptr).advanced(by: MemoryLayout<UInt64>.stride)
blobPtr = blobPtrPtr.load(as: UnsafeRawPointer.self)
}
return Data(bytes: blobPtr, count: Int(contentsSize))
}
}
}
// MARK: - Decimal
extension duckdb_decimal {
private static let scaleLimit = 38
init(_ source: Decimal) throws {
let mantissaLimit = source.isSignMinus ? 1 + UIntHuge(IntHuge.max) : UIntHuge(IntHuge.max)
var scale: Int
var mantissa: UIntHuge
if source.exponent > 0 {
let exponent = UIntHuge(source.exponent)
let (out, overflow) = source.hugeMantissa.multipliedReportingOverflow(by: exponent)
guard overflow == false else { throw DatabaseError.decimalUnrepresentable }
mantissa = out
scale = 0
}
else {
scale = -source.exponent
mantissa = source.hugeMantissa
while scale > Self.scaleLimit {
mantissa /= 10
scale -= 1
}
while scale > 0, mantissa > mantissaLimit {
mantissa /= 10
scale -= 1
}
}
guard mantissa <= mantissaLimit else {
throw DatabaseError.decimalUnrepresentable
}
guard mantissa > 0 else {
self = duckdb_decimal(width: 0, scale: 0, value: .init(lower: 0, upper: 0))
return
}
let value = source.isSignMinus
? IntHuge.min + IntHuge(mantissaLimit - mantissa) : IntHuge(mantissa)
self = duckdb_decimal(width: 38, scale: .init(scale), value: .init(value))
}
}
fileprivate extension Decimal {
var hugeMantissa: UIntHuge {
let components = [
_mantissa.0, _mantissa.1, _mantissa.2, _mantissa.3,
_mantissa.4, _mantissa.5, _mantissa.6, _mantissa.7
]
var mantissa = UIntHuge(0)
for i in 0..<Int(_length) {
mantissa += UIntHuge(components[i]) << (i * 16)
}
return mantissa
}
}
// MARK: - Vector
extension duckdb_vector {
var logicalType: LogicalType {
LogicalType { duckdb_vector_get_column_type(self) }
}
}

View File

@@ -0,0 +1,47 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
@_implementationOnly import Cduckdb
import Foundation
final class DataChunk: Sendable {
var count: DBInt { duckdb_data_chunk_get_size(ptr.pointee) }
var columnCount: DBInt { duckdb_data_chunk_get_column_count(ptr.pointee) }
private let ptr = UnsafeMutablePointer<duckdb_data_chunk?>.allocate(capacity: 1)
init(cresult: duckdb_result, index: DBInt) {
self.ptr.pointee = duckdb_result_get_chunk(cresult, index)!
}
deinit {
duckdb_destroy_data_chunk(ptr)
ptr.deallocate()
}
func withVector<T>(at index: DBInt, _ body: (Vector) throws -> T) rethrows -> T {
try body(Vector(duckdb_data_chunk_get_vector(ptr.pointee, index), count: Int(count)))
}
}

View File

@@ -0,0 +1,48 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
import Foundation
protocol DecimalStorageType {
var asDecimal: Decimal { get }
}
extension Int8: DecimalStorageType {
var asDecimal: Decimal { Decimal(self) }
}
extension Int16: DecimalStorageType {
var asDecimal: Decimal { Decimal(self) }
}
extension Int32: DecimalStorageType {
var asDecimal: Decimal { Decimal(self) }
}
extension Int64: DecimalStorageType {
var asDecimal: Decimal { Decimal(self) }
}
extension IntHuge: DecimalStorageType {
var asDecimal: Decimal { Decimal(self) }
}

View File

@@ -0,0 +1,33 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
import Foundation
extension Optional where Wrapped: StringProtocol {
func withOptionalCString<T>(_ body: (UnsafePointer<CChar>?) throws -> T) rethrows -> T {
guard let string = self else { return try body(nil) }
return try string.withCString(body)
}
}

View File

@@ -0,0 +1,71 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
protocol PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { get }
}
extension Bool: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .boolean }
}
extension Int8: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .tinyint }
}
extension Int16: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .smallint }
}
extension Int32: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .integer }
}
extension Int64: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .bigint }
}
extension UInt8: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .utinyint }
}
extension UInt16: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .usmallint }
}
extension UInt32: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .uinteger }
}
extension UInt64: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .ubigint }
}
extension Float: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .float }
}
extension Double: PrimitiveDatabaseValue {
static var representedDatabaseTypeID: DatabaseType { .double }
}

View File

@@ -0,0 +1,340 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
@_implementationOnly import Cduckdb
import Foundation
struct Vector {
static let vectorSize = DBInt(duckdb_vector_size())
let count: Int
let offset: Int
let logicalType: LogicalType
private let cvector: duckdb_vector
init(_ cvector: duckdb_vector, count: Int, offset: Int = 0, logicalType: LogicalType? = nil) {
self.count = count
self.offset = offset
self.cvector = cvector
self.logicalType = logicalType ?? cvector.logicalType
}
func withCVector<T>(_ body: (duckdb_vector) throws -> T) rethrows -> T {
try body(cvector)
}
}
extension Vector {
func unwrapNull(at index: Int) -> Bool {
precondition(index < count, "vector index out of bounds")
let offsetIndex = offset + index
let validityMasksPtr = duckdb_vector_get_validity(cvector)
guard let validityMasksPtr else { return false }
let validityMaskEntryIndex = offsetIndex / 64
let validityMaskEntryPtr = (validityMasksPtr + validityMaskEntryIndex)
let validityBitIndex = offsetIndex % 64
let validityBit = (DBInt(1) << validityBitIndex)
return validityMaskEntryPtr.pointee & validityBit == 0
}
func unwrap(_ type: Int.Type, at index: Int) throws -> Int {
switch logicalType.dataType {
case .tinyint:
return Int(try unwrap(Int8.self, at: index))
case .smallint:
return Int(try unwrap(Int16.self, at: index))
case .integer:
return Int(try unwrap(Int32.self, at: index))
case .bigint:
return Int(try unwrap(Int64.self, at: index))
default:
throw DatabaseError.typeMismatch(Int.self)
}
}
func unwrap(_ type: UInt.Type, at index: Int) throws -> UInt {
switch logicalType.dataType {
case .utinyint:
return UInt(try unwrap(UInt8.self, at: index))
case .usmallint:
return UInt(try unwrap(UInt16.self, at: index))
case .uinteger:
return UInt(try unwrap(UInt32.self, at: index))
case .ubigint:
return UInt(try unwrap(UInt64.self, at: index))
default:
throw DatabaseError.typeMismatch(Int.self)
}
}
func unwrap<T: PrimitiveDatabaseValue>(_ type: T.Type, at index: Int) throws -> T {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: T.representedDatabaseTypeID)
return unsafelyUnwrapElement(as: T.self, at: index)
}
func unwrap(_ type: String.Type, at index: Int) throws -> String {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .varchar)
return unsafelyUnwrapElement(as: duckdb_string.self, at: index) { $0.asString }
}
func unwrap(_ type: IntHuge.Type, at index: Int) throws -> IntHuge {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .hugeint)
return unsafelyUnwrapElement(as: duckdb_hugeint.self, at: index) { $0.asIntHuge }
}
func unwrap(_ type: UIntHuge.Type, at index: Int) throws -> UIntHuge {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .uhugeint)
return unsafelyUnwrapElement(as: duckdb_uhugeint.self, at: index) { $0.asUIntHuge }
}
func unwrap(_ type: UUID.Type, at index: Int) throws -> UUID {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .uuid)
return unsafelyUnwrapElement(as: duckdb_hugeint.self, at: index) { $0.asUUID }
}
func unwrap(_ type: Time.Type, at index: Int) throws -> Time {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .time)
return unsafelyUnwrapElement(as: duckdb_time.self, at: index) { $0.asTime }
}
func unwrap(_ type: TimeTz.Type, at index: Int) throws -> TimeTz {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .timeTz)
return unsafelyUnwrapElement(as: duckdb_time_tz.self, at: index) { $0.asTime }
}
func unwrap(_ type: Date.Type, at index: Int) throws -> Date {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .date)
return unsafelyUnwrapElement(as: duckdb_date.self, at: index) { $0.asDate }
}
func unwrap(_ type: Interval.Type, at index: Int) throws -> Interval {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .interval)
return unsafelyUnwrapElement(as: duckdb_interval.self, at: index) { $0.asInterval }
}
func unwrap(_ type: Timestamp.Type, at index: Int) throws -> Timestamp {
let columnTypes = [DatabaseType.timestampS, .timestampMS, .timestamp, .timestampTz, .timestampNS]
try assertNonNullTypeMatch(of: type, at: index, withColumnTypes: .init(columnTypes))
return unsafelyUnwrapElement(as: duckdb_timestamp.self, at: index) { ctimestamp in
switch logicalType.dataType {
case .timestampS:
let scaled = duckdb_timestamp(micros: ctimestamp.micros * 1_000_000)
return scaled.asTimestamp
case .timestampMS:
let scaled = duckdb_timestamp(micros: ctimestamp.micros * 1_000)
return scaled.asTimestamp
case .timestampNS:
let scaled = duckdb_timestamp(micros: ctimestamp.micros / 1_000)
return scaled.asTimestamp
default:
return ctimestamp.asTimestamp
}
}
}
func unwrap(_ type: Data.Type, at index: Int) throws -> Data {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .blob)
return unsafelyUnwrapElement(as: duckdb_blob.self, at: index) { $0.asData }
}
func unwrap(_ type: Decimal.Type, at index: Int) throws -> Decimal {
try assertNonNullTypeMatch(of: type, at: index, withColumnType: .decimal)
guard let props = logicalType.decimalProperties else {
fatalError("expected decimal logical type")
}
switch props.storageType {
case .tinyint:
return unwrapDecimal(withUnderlyingType: Int8.self, scale: props.scale, at: index)
case .smallint:
return unwrapDecimal(withUnderlyingType: Int16.self, scale: props.scale, at: index)
case .integer:
return unwrapDecimal(withUnderlyingType: Int32.self, scale: props.scale, at: index)
case .bigint:
return unwrapDecimal(withUnderlyingType: Int64.self, scale: props.scale, at: index)
case .hugeint:
return unwrapDecimal(withUnderlyingType: IntHuge.self, scale: props.scale, at: index)
case let unexpectedInternalType:
fatalError("unexpected internal decimal type: \(unexpectedInternalType)")
}
}
func unwrapDecimal<T: DecimalStorageType>(
withUnderlyingType storageType: T.Type, scale: UInt8, at index: Int
) -> Decimal {
unsafelyUnwrapElement(as: T.self, at: index) { decimalStorage in
let storageValue = decimalStorage.asDecimal
let sign = storageValue.sign
let exponent = -Int(scale)
let significand = abs(storageValue)
return Decimal(sign: sign, exponent: exponent, significand: significand)
}
}
func unsafelyUnwrapElement<T>(as type: T.Type, at index: Int) -> T {
unsafelyUnwrapElement(as: type, at: index) { $0 }
}
func unsafelyUnwrapElement<T, U>(
as type: T.Type, at index: Int, transform: (T) -> U
) -> U {
precondition(index < count, "vector index out of bounds")
let offsetIndex = offset + index
let dataPtr = duckdb_vector_get_data(cvector)!
let itemDataPtr = dataPtr.assumingMemoryBound(to: T.self)
return transform(itemDataPtr.advanced(by: offsetIndex)[0])
}
func assertNonNullTypeMatch<T>(
of type: T.Type, at index: Int, withColumnType columnType: DatabaseType
) throws {
try assertNonNullTypeMatch(of: type, at: index, withColumnTypes: .init([columnType]))
}
func assertNonNullTypeMatch<T>(
of type: T.Type, at index: Int, withColumnTypes columnTypes: Set<DatabaseType>
) throws {
guard unwrapNull(at: index) == false else {
throw DatabaseError.valueNotFound(type)
}
guard columnTypes.contains(logicalType.underlyingDataType) else {
throw DatabaseError.typeMismatch(type)
}
}
}
// MARK: - Collection Conformance
extension Vector: Collection {
struct Element {
let vector: Vector
let index: Int
}
public struct Iterator: IteratorProtocol {
private let vector: Vector
private var position: Int
init(_ vector: Vector) {
self.vector = vector
self.position = 0
}
public mutating func next() -> Element? {
guard position < vector.count else { return nil }
defer { position += 1 }
return vector[position]
}
}
public var startIndex: Int { 0 }
public var endIndex: Int { count }
public subscript(position: Int) -> Element { Element(vector: self, index: position) }
public func makeIterator() -> Iterator { Iterator(self) }
public func index(after i: Int) -> Int { i + 1 }
public func index(before i: Int) -> Int { i - 1 }
}
// MARK: - Element Accessors
extension Vector.Element {
var dataType: DatabaseType { vector.logicalType.dataType }
var logicalType: LogicalType { vector.logicalType }
func unwrapNull() -> Bool { vector.unwrapNull(at: index) }
func unwrap(_ type: Int.Type) throws -> Int { try vector.unwrap(type, at: index) }
func unwrap(_ type: UInt.Type) throws -> UInt { try vector.unwrap(type, at: index) }
func unwrap<T: PrimitiveDatabaseValue>(_ type: T.Type) throws -> T { try vector.unwrap(type, at: index) }
func unwrap(_ type: String.Type) throws -> String { try vector.unwrap(type, at: index) }
func unwrap(_ type: IntHuge.Type) throws -> IntHuge { try vector.unwrap(type, at: index) }
func unwrap(_ type: UIntHuge.Type) throws -> UIntHuge { try vector.unwrap(type, at: index) }
func unwrap(_ type: UUID.Type) throws -> UUID { try vector.unwrap(type, at: index) }
func unwrap(_ type: Time.Type) throws -> Time { try vector.unwrap(type, at: index) }
func unwrap(_ type: Date.Type) throws -> Date { try vector.unwrap(type, at: index) }
func unwrap(_ type: Interval.Type) throws -> Interval { try vector.unwrap(type, at: index) }
func unwrap(_ type: Timestamp.Type) throws -> Timestamp { try vector.unwrap(type, at: index) }
func unwrap(_ type: Data.Type) throws -> Data { try vector.unwrap(type, at: index) }
func unwrap(_ type: Decimal.Type) throws -> Decimal { try vector.unwrap(type, at: index) }
func unwrap(_ type: TimeTz.Type) throws -> TimeTz { try vector.unwrap(type, at: index) }
func unwrapDecodable<T: Decodable>(_ type: T.Type) throws -> T {
try VectorElementDecoder.default.decode(T.self, element: self)
}
}
// MARK: - Map Contents accessors
extension Vector.Element {
struct MapContent {
let keyVector: Vector
let valueVector: Vector
}
var childVector: Vector? {
guard let child = duckdb_list_vector_get_child(vector.cvector) else { return nil }
let count = duckdb_list_vector_get_size(vector.cvector)
let info = vector.unsafelyUnwrapElement(as: duckdb_list_entry_t.self, at: vector.offset + index)
precondition(info.offset + info.length <= count)
return Vector(child, count: Int(info.length), offset: Int(info.offset))
}
var mapContents: MapContent? {
guard dataType == .map else { return nil }
guard let childVector = childVector else { return nil }
guard let keys = duckdb_struct_vector_get_child(childVector.cvector, 0) else { return nil }
guard let values = duckdb_struct_vector_get_child(childVector.cvector, 1) else { return nil }
let keyVector = Vector(keys, count: childVector.count, offset: childVector.offset)
let valueVector = Vector(values, count: childVector.count, offset: childVector.offset)
return MapContent(keyVector: keyVector, valueVector: valueVector)
}
}
// MARK: - Struct Contents accesors
extension Vector.Element {
struct StructMemberContent {
let name: String
let vector: Vector
}
var structContents: [StructMemberContent]? {
guard let properties = vector.logicalType.structMemberProperties else { return nil }
var content = [StructMemberContent]()
for (i, member) in properties.enumerated() {
let memberCVector = duckdb_struct_vector_get_child(vector.cvector, DBInt(i))!
let memberVector = Vector(memberCVector, count: vector.count, offset: vector.offset)
content.append(.init(name: member.name, vector: memberVector))
}
return content
}
}

View File

@@ -0,0 +1,547 @@
//
// DuckDB
// https://github.com/duckdb/duckdb-swift
//
// Copyright © 2018-2024 Stichting DuckDB Foundation
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
@_implementationOnly import Cduckdb
import Foundation
// MARK: - Top Level Decoder
final class VectorElementDecoder {
static let `default` = VectorElementDecoder()
func decode<T: Decodable>(_ type: T.Type, element: Vector.Element) throws -> T {
try T(from: VectorElementDataDecoder(element: element))
}
}
// MARK: - Vector Decoder
fileprivate struct VectorElementDataDecoder: Decoder {
struct VectorDecoderCodingKey: CodingKey {
let stringValue: String
let intValue: Int?
init(stringValue: String) {
self.intValue = nil
self.stringValue = stringValue
}
init(intValue: Int) {
self.intValue = intValue
self.stringValue = "\(intValue)"
}
}
let codingPath: [CodingKey]
let element: Vector.Element
let userInfo: [CodingUserInfoKey : Any]
init(element: Vector.Element, codingPath: [CodingKey] = []) {
self.codingPath = codingPath
self.element = element
self.userInfo = [CodingUserInfoKeys.logicalTypeCodingUserInfoKey: element.logicalType]
}
func container<Key: CodingKey>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
switch element.dataType {
case .map:
return .init(
try KeyedValueContainer<Key>.createMapContainer(decoder: self, element: element)
)
case .struct:
return .init(
try KeyedValueContainer<Key>.createStructContainer(decoder: self, element: element)
)
default:
let columnType = element.dataType
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot create keyed decoding container for column type \(columnType)"
)
throw DecodingError.typeMismatch(type, context)
}
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
switch element.dataType {
case .list:
return try UnkeyedValueContainer.createListContainer(decoder: self, element: element)
default:
let columnType = element.dataType
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot create unkeyed decoding container for column type \(columnType)"
)
throw DecodingError.typeMismatch(UnkeyedDecodingContainer.self, context)
}
}
func singleValueContainer() -> SingleValueDecodingContainer {
SingleValueContainer(decoder: self, codingPath: codingPath, element: element)
}
}
// MARK: - Single Value Container
extension VectorElementDataDecoder {
struct SingleValueContainer: SingleValueDecodingContainer {
let decoder: VectorElementDataDecoder
var codingPath: [CodingKey]
let element: Vector.Element
init(decoder: VectorElementDataDecoder, codingPath: [CodingKey], element: Vector.Element) {
self.decoder = decoder
self.codingPath = codingPath
self.element = element
}
// Protocol conforming `decode(_:)` implementations
func decodeNil() -> Bool {
element.unwrapNull()
}
func decode(_ type: Int.Type) throws -> Int {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Int8.Type) throws -> Int8 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Int16.Type) throws -> Int16 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Int32.Type) throws -> Int32 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Int64.Type) throws -> Int64 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UInt.Type) throws -> UInt {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UInt8.Type) throws -> UInt8 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UInt16.Type) throws -> UInt16 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UInt32.Type) throws -> UInt32 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UInt64.Type) throws -> UInt64 {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Float.Type) throws -> Float {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Double.Type) throws -> Double {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: String.Type) throws -> String {
try attemptDecode { try element.unwrap(type) }
}
// Special case `decode(_:)` implementations
func decode(_ type: IntHuge.Type) throws -> IntHuge {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UIntHuge.Type) throws -> UIntHuge {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Decimal.Type) throws -> Decimal {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Data.Type) throws -> Data {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: UUID.Type) throws -> UUID {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Date.Type) throws -> Date {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Time.Type) throws -> Time {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: TimeTz.Type) throws -> TimeTz {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Timestamp.Type) throws -> Timestamp {
try attemptDecode { try element.unwrap(type) }
}
func decode(_ type: Interval.Type) throws -> Interval {
try attemptDecode { try element.unwrap(type) }
}
// Generic decode
func decode<T: Decodable>(_ type: T.Type) throws -> T {
switch type {
case is IntHuge.Type:
return try decode(IntHuge.self) as! T
case is UIntHuge.Type:
return try decode(UIntHuge.self) as! T
case is Decimal.Type:
return try decode(Decimal.self) as! T
case is Data.Type:
return try decode(Data.self) as! T
case is UUID.Type:
return try decode(UUID.self) as! T
case is Date.Type:
return try decode(Date.self) as! T
case is Time.Type:
return try decode(Time.self) as! T
case is Timestamp.Type:
return try decode(Timestamp.self) as! T
case is Interval.Type:
return try decode(Interval.self) as! T
case is TimeTz.Type:
return try decode(TimeTz.self) as! T
default:
return try T(from: decoder)
}
}
private func attemptDecode<T>(_ body: () throws -> T) throws -> T {
do {
return try body()
}
catch DatabaseError.valueNotFound(let type) {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected value of type \(type) found null value instead"
)
throw DecodingError.valueNotFound(type, context)
}
catch DatabaseError.typeMismatch(let type) {
let columnType = element.dataType
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected to decode \(type) but found \(columnType) instead."
)
throw DecodingError.typeMismatch(type, context)
}
}
}
}
// MARK: - Unkeyed Container
extension VectorElementDataDecoder {
struct UnkeyedValueContainer: UnkeyedDecodingContainer {
let decoder: VectorElementDataDecoder
let codingPath: [CodingKey]
let vector: Vector
let count: Int?
var currentIndex = 0
var isAtEnd: Bool { currentIndex >= vector.count }
init(
decoder: VectorElementDataDecoder,
codingPath: [CodingKey],
vector: Vector
) {
self.decoder = decoder
self.codingPath = codingPath
self.vector = vector
self.count = vector.count
}
func decodeNil() -> Bool {
vector[currentIndex].unwrapNull()
}
mutating func decode<T: Decodable>(_ type: T.Type) throws -> T {
let decoder = try superDecoder()
let value = try T(from: decoder)
currentIndex += 1
return value
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type
) throws -> KeyedDecodingContainer<NestedKey> {
let decoder = try superDecoder()
return try decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer {
let decoder = try superDecoder()
return try decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
var codingPath = decoder.codingPath
codingPath.append(VectorDecoderCodingKey(intValue: currentIndex))
return VectorElementDataDecoder(element: vector[currentIndex], codingPath: codingPath)
}
}
}
// MARK: - Keyed Container
extension VectorElementDataDecoder {
struct KeyedValueContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
struct Properties {
let keyMap: [String: KeyPath]
let vectors: [Vector]
}
struct KeyPath {
let key: Key
let vectorIndex: Int
let rowIndex: Int
}
let decoder: VectorElementDataDecoder
let codingPath: [CodingKey]
let keyMap: [String: KeyPath]
let vectors: [Vector]
init(
decoder: VectorElementDataDecoder,
codingPath: [CodingKey],
keyMap: [String: KeyPath],
vectors: [Vector]
) {
self.decoder = decoder
self.codingPath = codingPath
self.keyMap = keyMap
self.vectors = vectors
}
var allKeys: [Key] { keyMap.values.map { $0.key } }
func contains(_ key: Key) -> Bool {
keyMap[key.stringValue] != nil
}
func decodeNil(forKey key: Key) throws -> Bool {
let path = try unwrapPath(forKey: key)
let vector = vectors[path.vectorIndex]
return vector[path.rowIndex].unwrapNull()
}
func decode<T: Decodable>(_ type: T.Type, forKey key: Key) throws -> T {
let decoder = try superDecoder(forKey: key)
return try T(from: decoder)
}
func nestedContainer<NestedKey: CodingKey>(
keyedBy type: NestedKey.Type, forKey key: Key
) throws -> KeyedDecodingContainer<NestedKey> {
let decoder = try superDecoder(forKey: key)
return try decoder.container(keyedBy: type)
}
func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
let decoder = try superDecoder(forKey: key)
return try decoder.unkeyedContainer()
}
func superDecoder() throws -> Decoder {
VectorElementDataDecoder(element: decoder.element, codingPath: decoder.codingPath)
}
func superDecoder(forKey key: Key) throws -> Decoder {
let path = try unwrapPath(forKey: key)
let vector = vectors[path.vectorIndex]
var codingPath = decoder.codingPath
codingPath.append(VectorDecoderCodingKey(stringValue: key.stringValue))
return VectorElementDataDecoder(element: vector[path.rowIndex], codingPath: codingPath)
}
private func unwrapPath(forKey key: Key) throws -> KeyPath {
guard let path = keyMap[key.stringValue] else {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Key not found"
)
throw DecodingError.keyNotFound(key, context)
}
return path
}
}
}
// MARK: - Container Factories
fileprivate extension VectorElementDataDecoder.KeyedValueContainer {
static func createMapContainer(
decoder: VectorElementDataDecoder, element: Vector.Element
) throws -> Self {
guard element.unwrapNull() == false else {
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Cannot get keyed decoding container found null value instead"
)
throw DecodingError.valueNotFound(Self.self, context)
}
guard element.dataType == .map else {
let columnType = element.dataType
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Expected map column type, found \(columnType) column type instead."
)
throw DecodingError.typeMismatch(Self.self, context)
}
guard let mapContent = element.mapContents else {
fatalError("Internal consistency error. Expected map content in vector.")
}
let keys: [Key]
switch mapContent.keyVector.logicalType.dataType {
case .varchar:
let stringKeys = try mapContent.keyVector.map { try $0.unwrap(String.self) }
keys = try stringKeys.map { try createKey(stringValue: $0) }
case .tinyint, .smallint, .integer, .bigint:
let intKeys = try mapContent.keyVector.map { try $0.unwrap(Int.self) }
keys = try intKeys.map { try createKey(intValue: $0) }
default:
let context = DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Cannot decode map with non string/integer key type"
)
throw DecodingError.typeMismatch(Key.self, context)
}
let vectors = [mapContent.valueVector]
let mapKeys = keys.map(\.stringValue)
let mapValues = keys.enumerated().map { KeyPath(key: $0.1, vectorIndex: 0, rowIndex: $0.0) }
let keyMap = Dictionary(uniqueKeysWithValues: zip(mapKeys, mapValues))
return Self(decoder: decoder, codingPath: decoder.codingPath, keyMap: keyMap, vectors: vectors)
}
static func createStructContainer(
decoder: VectorElementDataDecoder, element: Vector.Element
) throws -> Self {
let codingPath = decoder.codingPath
let dataType = element.dataType
guard element.unwrapNull() == false else {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot get keyed decoding container found null value instead"
)
throw DecodingError.valueNotFound(Self.self, context)
}
guard dataType == .struct else {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected struct column type, found \(dataType) column type instead."
)
throw DecodingError.typeMismatch(Self.self, context)
}
guard let structContents = element.structContents else {
fatalError("Internal consistency error. Expected struct content in vector.")
}
let keys = try structContents.map(\.name).map(Self.createKey(stringValue:))
let vectors = structContents.map(\.vector)
var keyMap = [String: KeyPath]()
for (i, key) in keys.enumerated() {
keyMap[key.stringValue] = .init(key: key, vectorIndex: i, rowIndex: element.index)
}
return Self(decoder: decoder, codingPath: codingPath, keyMap: keyMap, vectors: vectors)
}
private static func createKey(stringValue: String) throws -> Key {
guard let key = Key(stringValue: stringValue) else {
let context = DecodingError.Context(
codingPath: [],
debugDescription: "Cannot instatiate key of type \(Key.self) with string: \(stringValue)"
)
throw DecodingError.typeMismatch(Key.self, context)
}
return key
}
private static func createKey(intValue: Int) throws -> Key {
guard let key = Key(intValue: intValue) else {
let context = DecodingError.Context(
codingPath: [],
debugDescription: "Cannot instatiate key of type \(Key.self) with integer: \(intValue)"
)
throw DecodingError.typeMismatch(Key.self, context)
}
return key
}
}
fileprivate extension VectorElementDataDecoder.UnkeyedValueContainer {
static func createListContainer(
decoder: VectorElementDataDecoder, element: Vector.Element
) throws -> Self {
let codingPath = decoder.codingPath
let dataType = element.dataType
guard element.unwrapNull() == false else {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Cannot get unkeyed decoding container found null value instead"
)
throw DecodingError.valueNotFound(Self.self, context)
}
guard dataType == .list else {
let context = DecodingError.Context(
codingPath: codingPath,
debugDescription: "Expected list column type, found \(dataType) column type instead."
)
throw DecodingError.typeMismatch(Self.self, context)
}
guard let childVector = element.childVector else {
fatalError("Internal consistency error. Expected list content in vector.")
}
return Self(decoder: decoder, codingPath: codingPath, vector: childVector)
}
}