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,432 @@
//
// 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
/// An object that efficiently appends data to a DuckDB table
///
/// Appenders are the most efficient way of loading data into DuckDB from within
/// Swift, and are recommended for fast data loading. The appender is much
/// faster than using prepared statements or individual INSERT INTO statements.
///
/// Appends are made in row-wise format. For every column an `append(_:)` call
/// should be made after which the row should be finished by calling
/// ``endRow()``.
///
/// The following example shows two rows being appended to a table with a two
/// column layout.
///
/// ```swift
/// do {
/// let database = try Database(store: .inMemory)
/// let connection = try database.connect()
/// try connection.execute("CREATE TABLE people(id INTEGER, name VARCHAR)")
/// let appender = try Appender(connection: connection, table: "people")
/// // add first row
/// try appender.append(Int32(1))
/// try appender.append("Mark")
/// try appender.endRow()
/// // add second row
/// try appender.append(Int32(2))
/// try appender.append("Hannes")
/// try appender.endRow()
/// // flush rows to table
/// try appender.flush()
/// }
/// catch {
/// // handle error
/// }
/// ```
public final class Appender {
private let connection: Connection
private let ptr = UnsafeMutablePointer<duckdb_appender?>.allocate(capacity: 1)
/// Creates a new appender
///
/// Instantiates an ``Appender`` through which table rows can be efficiently
/// added
///
/// - Parameter connection: the connection through which rows should be
/// appended
/// - Parameter schema: the database schema (defaults to `main`)
/// - Parameter table: the table to append to
/// - Throws: ``DatabaseError/appenderFailedToInitialize(reason:)`` if the
/// appender failed to instantiate
public init(connection: Connection, schema: String? = nil, table: String) throws {
self.connection = connection
let status = schema.withOptionalCString { schemaStrPtr in
table.withCString { tableStrPtr in
connection.withCConnection { duckdb_appender_create($0, schemaStrPtr, tableStrPtr, ptr) }
}
}
guard .success == status else {
throw DatabaseError.appenderFailedToInitialize(reason: appenderError())
}
}
deinit {
try? flush()
duckdb_appender_destroy(ptr)
ptr.deallocate()
}
/// Signals the end of the current row
///
/// After all columns for a row have been appended, ``endRow()`` must be
/// called to indicate that the row is ready to be added to the database.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToEndRow(reason:)``
/// if the row could not be completed in its current state
public func endRow() throws {
let status = duckdb_appender_end_row(ptr.pointee)
guard .success == status else {
throw DatabaseError.appenderFailedToEndRow(reason: appenderError())
}
}
/// Flushes pending rows to the database
///
/// To optimize performance, the appender writes rows to the database in
/// batches. Use `flush()` to immediately write any rows that are pending
/// insertion.
///
/// - Throws: ``DatabaseError/appenderFailedToFlush(reason:)`` if the pending
/// rows failed to be written to the database
public func flush() throws {
let status = duckdb_appender_flush(ptr.pointee)
guard .success == status else {
throw DatabaseError.appenderFailedToFlush(reason: appenderError())
}
}
}
public extension Appender {
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Bool?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_bool(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Int8?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_int8(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Int16?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_int16(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Int32?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_int32(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Int64?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_int64(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: IntHuge?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_hugeint(ptr.pointee, .init(value)) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: UIntHuge?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_uhugeint(ptr.pointee, .init(value)) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: UInt8?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_uint8(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: UInt16?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_uint16(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: UInt32?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_uint32(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: UInt64?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_uint64(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Float?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_float(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Double?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_double(ptr.pointee, value) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Date?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_date(ptr.pointee, .init(date: value)) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Time?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand { duckdb_append_time(ptr.pointee, .init(time: value)) }
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Timestamp?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand {
duckdb_append_timestamp(ptr.pointee, .init(timestamp: value))
}
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Interval?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand {
duckdb_append_interval(ptr.pointee, .init(interval: value))
}
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: String?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
let data = value.data(using: .utf8)!
try withThrowingCommand {
data.withUnsafeBytes { dataPtr in
duckdb_append_varchar_length(ptr.pointee, dataPtr.baseAddress, .init(dataPtr.count))
}
}
}
/// Appends a value for the current row of the given type
///
/// Appends are made in row-wise format. For every column, an `append(_:)`
/// call should be made, after which the row should be finished by calling
/// ``endRow()``.
///
/// - Parameter value: the value to append
/// - Throws: ``DatabaseError/appenderFailedToAppendItem(reason:)``
/// if a value of this type was not expected in the appender's current state
func append(_ value: Data?) throws {
guard let value = try unwrapValueOrAppendNull(value) else { return }
try withThrowingCommand {
value.withUnsafeBytes { dataPtr in
duckdb_append_blob( ptr.pointee, dataPtr.baseAddress, .init(dataPtr.count)) }
}
}
}
private extension Appender {
func appendNullValue() throws {
try withThrowingCommand { duckdb_append_null(ptr.pointee) }
}
func unwrapValueOrAppendNull<T>(_ value: T?) throws -> T? {
guard let value else {
try appendNullValue()
return nil
}
return value
}
func withThrowingCommand(_ body: () throws -> duckdb_state) throws {
let state = try body()
guard state == .success else {
throw DatabaseError.appenderFailedToAppendItem(reason: appenderError())
}
}
func appenderError() -> String? {
duckdb_appender_error(ptr.pointee).map(String.init(cString:))
}
}

View File

@@ -0,0 +1,52 @@
//
// 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.
public struct CodingUserInfoKeys {
/// This key is set on the `userInfo` dictionary of the `Decoder` that is
/// used when transforming data into a `Decodable`. The value is the ``LogicalType``
/// of the element being decoded. This can be used to implement dynamic decoding
/// behavior based on the underlying database type.
///
/// For example:
/// ```swift
/// struct DynamicDecodable: Decodable {
/// init(from decoder: Decoder) throws {
/// guard let logicalType = decoder.userInfo[CodingUserInfoKeys.logicalType] as? LogicalType else {
/// throw Error.expectedLogicalType
/// }
/// switch logicalType.dataType {
/// case .list:
/// let unkeyedContainer = try decoder.unkeyedContainer()
/// ...
/// case .map, .struct:
/// let keyedContainer = try decoder.container(keyedBy: AnyCodingKey.self)
/// ...
/// }
/// }
/// }
///
/// let column = result[0].cast(to: DynamicDecodable.self)
/// ```
public static let logicalTypeCodingUserInfoKey = CodingUserInfoKey(rawValue: "logicalType")!
}

View File

@@ -0,0 +1,466 @@
//
// 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
/// A DuckDB result set column
///
/// DuckDB columns represent a vertical slice of a result set table. All DuckDB
/// columns have an underlying database type (accessed via the
/// ``underlyingDatabaseType`` member) which determine the native Swift types to
/// which the column can be cast to.
///
/// When columns are initially retrieved from a ``ResultSet`` through its
/// ``ResultSet/subscript(_:)`` accessor they have an element type of `Void`.
/// Only after a column is cast to a matching native type can its elements be
/// accessed.
///
/// For example, a column with an underlying database type of
/// ``DatabaseType/varchar`` can be cast to type `String`:
///
/// ```swift
/// // casts the first column in a result set to string
/// let column = result[0].cast(to: String.self)
/// ```
///
/// The documentation for each ``DatabaseType`` member specifies which native
/// Swift types a column may be cast to.
///
/// As a column is a Swift `Collection` type, once a column has been
/// successfully cast its elements can be accessed in the same way as any other
/// Swift collection type.
///
/// ```swift
/// for element in column {
/// print("element: \(element)")
/// }
/// ```
public struct Column<DataType> {
private let result: ResultSet
private let columnIndex: DBInt
private let unwrap: @Sendable (Vector.Element) throws -> DataType?
init(
result: ResultSet,
columnIndex: DBInt,
unwrap: @escaping @Sendable (Vector.Element) throws -> DataType?
) {
self.result = result
self.columnIndex = columnIndex
self.unwrap = unwrap
}
/// The name of the table column
public var name: String {
result.columnName(at: columnIndex)
}
/// The native Swift type to which the column has been cast
public var dataType: DataType.Type {
DataType.self
}
/// The underlying primitive database type of the column
public var underlyingDatabaseType: DatabaseType {
result.columnDataType(at: columnIndex)
}
/// The underlying logical type of the column
public var underlyingLogicalType: LogicalType {
result.columnLogicalType(at: columnIndex)
}
}
// MARK: - Type Casting
public extension Column {
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Void.Type) -> Column<Void> {
.init(result: result, columnIndex: columnIndex) { $0.unwrapNull() ? nil : () }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Bool.Type) -> Column<Bool> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Bool.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Warning: Implicit conversion of a DuckDB integer column value greater
/// than `Int.max` or less than `Int.min` is a programmer error and will
/// result in a runtime precondition failure
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Int.Type) -> Column<Int> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Int.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Int8.Type) -> Column<Int8> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Int8.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Int16.Type) -> Column<Int16> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Int16.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Int32.Type) -> Column<Int32> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Int32.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Int64.Type) -> Column<Int64> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Int64.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: IntHuge.Type) -> Column<IntHuge> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(IntHuge.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UIntHuge.Type) -> Column<UIntHuge> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UIntHuge.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Warning: Implicit conversion of a DuckDB integer column value greater
/// than `UInt.max` is a programmer error and will result in a runtime
/// precondition failure
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UInt.Type) -> Column<UInt> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UInt.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UInt8.Type) -> Column<UInt8> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UInt8.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UInt16.Type) -> Column<UInt16> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UInt16.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UInt32.Type) -> Column<UInt32> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UInt32.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UInt64.Type) -> Column<UInt64> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UInt64.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Float.Type) -> Column<Float> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Float.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Double.Type) -> Column<Double> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Double.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: String.Type) -> Column<String> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(String.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: UUID.Type) -> Column<UUID> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(UUID.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Time.Type) -> Column<Time> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Time.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between the
/// given type and the column's underlying database type, returned elements
/// will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: TimeTz.Type) -> Column<TimeTz> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(TimeTz.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Date.Type) -> Column<Date> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Date.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Timestamp.Type) -> Column<Timestamp> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Timestamp.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Interval.Type) -> Column<Interval> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Interval.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Data.Type) -> Column<Data> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Data.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast(to type: Decimal.Type) -> Column<Decimal> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrap(Decimal.self) }
}
/// Casts the column to the given type
///
/// A column cast always succeeds but if there is a type-mismatch between
/// the given type and the column's underlying database type, returned
/// elements will always be equal to `nil`.
///
/// - Parameter type: the native Swift type to cast to
/// - Returns: a typed DuckDB result set ``Column``
func cast<T: Decodable>(to type: T.Type) -> Column<T> {
.init(result: result, columnIndex: columnIndex) { try $0.unwrapDecodable(T.self) }
}
}
// MARK: - Collection conformance
extension Column: RandomAccessCollection {
public typealias Element = DataType?
public struct Iterator: IteratorProtocol {
private let column: Column
private var position: DBInt
init(column: Column) {
self.column = column
self.position = column.startIndex
}
public mutating func next() -> Element? {
guard position < column.endIndex else { return nil }
defer { position += 1 }
return .some(column[position])
}
}
public subscript(position: DBInt) -> DataType? {
try? unwrap(result.element(forColumn: columnIndex, at: position))
}
public var startIndex: DBInt { 0 }
public var endIndex: DBInt { result.rowCount }
public func makeIterator() -> Iterator {
Iterator(column: self)
}
public func index(after i: DBInt) -> DBInt { i + 1 }
public func index(before i: DBInt) -> DBInt { i - 1 }
}
// MARK: - Sendable conformance
extension Column: Sendable where DataType: Sendable {}
// MARK: - Identifiable conformance
extension Column: Identifiable {
public var id: String { name }
}

View File

@@ -0,0 +1,133 @@
//
// 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
public extension Database {
/// An object representing a DuckDB database configuration
///
/// Configuration options can be provided to change different settings of the
/// database system. Note that many of these settings can be changed later on
/// using PRAGMA statements as well. The configuration object should be
/// created, filled with values using ``setValue(_:forKey:)`` and passed to
/// ``Database/init(store:configuration:)``
///
/// The following example sets up an in-memory database with some
/// configuration options set
///
/// ```swift
/// do {
/// let configuration = Configuration()
/// configuration.setValue("READ_WRITE", forKey: "access_mode")
/// configuration.setValue("8", forKey: "threads")
/// configuration.setValue("8GB", forKey: "max_memory")
/// configuration.setValue("DESC", forKey: "default_order")
/// let database = try Database(store: .inMemory, configuration: configuration)
/// let connection = try database.connect()
/// }
/// catch {
/// // handle error
/// }
/// ```
/// Use ``Database/Configuration/options`` to see the full list of available
/// configuration options.
final class Configuration {
private let ptr = UnsafeMutablePointer<duckdb_config?>.allocate(capacity: 1)
/// Creates a DuckDB configuration
public init() {
let status = duckdb_create_config(ptr)
guard status == .success else { fatalError("malloc failure") }
}
deinit {
duckdb_destroy_config(ptr)
ptr.deallocate()
}
func withCConfiguration<T>(_ body: (duckdb_config?) throws -> T) rethrows -> T {
try body(ptr.pointee)
}
/// Set a value on the configuration object
///
/// Applies a configuration option to the configuration object. Use
/// ``Database/Configuration/options`` to see the full list of available
/// configuration options.
///
/// - Parameter key: the name of the option being set
/// - Parameter value: the desired value of the option being set
/// - Throws: ``DatabaseError/configurationFailedToSetFlag`` if the option
/// is not available or the value is malformed
public func setValue(_ value: String, forKey key: String) throws {
try value.withCString { valueCStr in
try key.withCString { keyCStr in
let status = duckdb_set_config(ptr.pointee, keyCStr, valueCStr)
guard .success == status else {
throw DatabaseError.configurationFailedToSetFlag
}
}
}
}
}
}
// MARK: - Option
public extension Database.Configuration {
/// A DuckDB database configuration option information type
struct OptionInfo {
/// The name/key of the option
public let name: String
/// An overview of the option and its potential values
public let description: String
}
/// A list of all available database configuration options with their
/// descriptions
static var options: [OptionInfo] {
let count = duckdb_config_count()
var options = [OptionInfo]()
let outName = UnsafeMutablePointer<UnsafePointer<CChar>?>.allocate(capacity: 1)
let outDesc = UnsafeMutablePointer<UnsafePointer<CChar>?>.allocate(capacity: 1)
defer {
outName.deallocate()
outDesc.deallocate()
}
for i in 0..<count {
duckdb_get_config_flag(i, outName, outDesc)
if let nameCStr = outName.pointee, let descCStr = outDesc.pointee {
let name = String(cString: nameCStr)
let desc = String(cString: descCStr)
options.append(OptionInfo(name: name, description: desc))
}
outName.pointee = nil
outDesc.pointee = nil
}
return options
}
}

View File

@@ -0,0 +1,106 @@
//
// 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
/// An object representing a connection to a DuckDB database
///
/// A connection through which a database can be queried
///
/// For each database, you can create one or many connections using
/// ``Database/connect()``.
/// As individual connections are locked during querying it is recommended that
/// in contexts where blocking is undesirable, connections should be accessed
/// asynchronously through an actor or via a background queue.
///
/// The following example creates a new in-memory database and connects to it.
///
/// ```swift
/// do {
/// let database = try Database(store: .inMemory)
/// let connection = try database.connect()
/// }
/// catch {
/// // handle error
/// }
/// ```
public final class Connection: Sendable {
private let database: Database
private let ptr = UnsafeMutablePointer<duckdb_connection?>.allocate(capacity: 1)
/// Creates a new connection
///
/// Instantiates a connection through which a database can be queried
///
/// - Parameter database: the database to connect to
/// - Throws: ``DatabaseError/connectionFailedToInitialize`` if the connection
/// failed to instantiate
public init(database: Database) throws {
self.database = database
let status = database.withCDatabase { duckdb_connect($0, ptr) }
guard status == .success else { throw DatabaseError.connectionFailedToInitialize }
}
deinit {
duckdb_disconnect(ptr)
ptr.deallocate()
}
/// Perform a database query
///
/// Takes a raw SQL statement and passes it to the database engine for
/// execution, returning the result.
///
/// - Note: This is a blocking operation
/// - Parameter sql: the query string as SQL
/// - Throws: ``DatabaseError/connectionQueryError(reason:)`` if the query
/// did not execute successfully
/// - Returns: a query result set
public func query(_ sql: String) throws -> ResultSet {
try ResultSet(connection: self, sql: sql)
}
/// Execute a database query
///
/// Takes a raw SQL statement and passes it to the database engine for
/// execution, ignoring the result.
///
/// - Note: This is a blocking operation
/// - Parameter sql: the query string as SQL
/// - Throws: ``DatabaseError/connectionQueryError(reason:)`` if the query
/// did not execute successfully
public func execute(_ sql: String) throws {
let status = sql.withCString { queryStrPtr in
duckdb_query(ptr.pointee, queryStrPtr, nil)
}
guard status == .success else {
throw DatabaseError.connectionQueryError(reason: nil)
}
}
func withCConnection<T>(_ body: (duckdb_connection?) throws -> T) rethrows -> T {
try body(ptr.pointee)
}
}

View File

@@ -0,0 +1,135 @@
//
// 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
/// DuckDB index type
public typealias DBInt = UInt64
/// An object representing a DuckDB database
///
/// To use DuckDB, you must first initialize a DuckDB ``Database``.
/// ``Database/init(store:configuration:)`` takes as parameter the database
/// store type. The ``Database/Store/inMemory`` option can be used to create an
/// in-memory database. Note that for an in-memory database no data is persisted
/// to disk (i.e. all data is lost when you exit the process).
///
/// With the ``Database`` instantiated, you can create one or many DuckDB
/// ``Connection`` instances using ``Database/connect()``. As individual
/// connections are locked during querying it is recommended that in contexts
/// where blocking is undesirable, connections should be accessed
/// asynchronously through an actor or via a background queue.
///
/// The following example creates a new in-memory database and connects to it.
///
/// ```swift
/// do {
/// let database = try Database(store: .inMemory)
/// let connection = try database.connect()
/// }
/// catch {
/// // handle error
/// }
/// ```
public final class Database: Sendable {
/// Duck DB database store type
public enum Store {
/// A local file based database store
case file(at: URL)
/// An in-memory database store
case inMemory
}
private let ptr = UnsafeMutablePointer<duckdb_database?>.allocate(capacity: 1)
/// Creates a Duck DB database
///
/// A DuckDB database can be initilaized using either a local database file or
/// using an in-memory store
///
/// - Note: An in-memory database does not persist data to disk. All data is
/// lost when you exit the process.
/// - Parameter store: the store to initialize the database with
/// - Parameter configuration: the configuration to initialize the database
/// with
/// - Throws: ``DatabaseError/databaseFailedToInitialize(reason:)`` if the
/// database failed to instantiate
public convenience init(
store: Store = .inMemory, configuration: Configuration? = nil
) throws {
var fileURL: URL?
if case .file(let url) = store {
guard url.isFileURL else {
throw DatabaseError.databaseFailedToInitialize(
reason: "provided URL for database store file must be local")
}
fileURL = url
}
try self.init(path: fileURL?.path, config: configuration)
}
private init(path: String?, config: Configuration?) throws {
let outError = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>.allocate(capacity: 1)
defer { outError.deallocate() }
let status = path.withOptionalCString { strPtr in
if let config {
return config.withCConfiguration { cconfig in
duckdb_open_ext(strPtr, ptr, cconfig, outError)
}
}
else {
return duckdb_open_ext(strPtr, ptr, nil, outError)
}
}
guard status == .success else {
let error = outError.pointee.map { ptr in
defer { duckdb_free(ptr) }
return String(cString: ptr)
}
throw DatabaseError.databaseFailedToInitialize(reason: error)
}
}
deinit {
duckdb_close(ptr)
ptr.deallocate()
}
/// Creates a connection to the database
///
/// See ``Connection/init(database:)`` for further discussion
///
/// - Throws: ``DatabaseError/connectionFailedToInitialize`` if the connection
/// could not be instantiated
/// - Returns: a database connection
public func connect() throws -> Connection {
try .init(database: self)
}
func withCDatabase<T>(_ body: (duckdb_database?) throws -> T) rethrows -> T {
try body(ptr.pointee)
}
}

View File

@@ -0,0 +1,55 @@
//
// 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.
/// A DuckDB database error
public enum DatabaseError: Error {
/// Provided decimal overflows the internal database representaion
case decimalUnrepresentable
/// Supplied item could not be appended
case appenderFailedToAppendItem(reason: String?)
/// Row could not be ended with the appender in its current state
case appenderFailedToEndRow(reason: String?)
/// Appender could not be flushed in its current state
case appenderFailedToFlush(reason: String?)
/// Failed to instantiate appender
case appenderFailedToInitialize(reason: String?)
/// Failed to instantiate connection
case connectionFailedToInitialize
/// Failed to set flag on database configuration
case configurationFailedToSetFlag
/// Failed to execute query on connection
case connectionQueryError(reason: String?)
/// Failed to instantiate database
case databaseFailedToInitialize(reason: String?)
/// Failed to instantiate prepared statement
case preparedStatementFailedToInitialize(reason: String?)
/// Failed to bound value to prepared statement
case preparedStatementFailedToBindParameter(reason: String?)
/// Failed to execute prepared statement query
case preparedStatementQueryError(reason: String?)
/// Value of type could not be found
case valueNotFound(Any.Type)
/// Type does not match underlying database type
case typeMismatch(Any.Type)
}

View File

@@ -0,0 +1,158 @@
//
// 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
/// The underlying database type of a DuckDB column
///
/// DuckDB is a strongly typed database system. As such, every column has a
/// single type specified. This type is constant over the entire column. That is
/// to say, a column with an underlying type of ``DatabaseType/integer`` will
/// only contain ``DatabaseType/integer`` values.
///
/// DuckDB also supports columns of composite types. For example, it is possible
/// to define an array of integers (`INT[]`), which can be cast to `[Int32]`
/// within Swift using ``Column/cast(to:)-4376d``. It is also possible to
/// define database types as arbitrary structs (`ROW(i INTEGER, j VARCHAR)`),
/// which can be cast to their `Decodable` matching Swift type in the same way.
public struct DatabaseType: RawRepresentable, Hashable, Equatable {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
}
// MARK: - Public Types
public extension DatabaseType {
/// Boolean type castable to `Bool`
static let boolean = DatabaseType(rawValue: DUCKDB_TYPE_BOOLEAN.rawValue)
/// Integer type castable to `Int8`
static let tinyint = DatabaseType(rawValue: DUCKDB_TYPE_TINYINT.rawValue)
/// Integer type castable to `Int16`
static let smallint = DatabaseType(rawValue: DUCKDB_TYPE_SMALLINT.rawValue)
/// Integer type castable to `Int32`
static let integer = DatabaseType(rawValue: DUCKDB_TYPE_INTEGER.rawValue)
/// Integer type castable to `Int64`
static let bigint = DatabaseType(rawValue: DUCKDB_TYPE_BIGINT.rawValue)
/// Integer type castable to `HugeInt`
static let hugeint = DatabaseType(rawValue: DUCKDB_TYPE_HUGEINT.rawValue)
/// Integer type castable to `UHugeInt`
static let uhugeint = DatabaseType(rawValue: DUCKDB_TYPE_UHUGEINT.rawValue)
/// Unsigned integer type castable to `UInt8`
static let utinyint = DatabaseType(rawValue: DUCKDB_TYPE_UTINYINT.rawValue)
/// Unsigned integer type castable to `UInt16`
static let usmallint = DatabaseType(rawValue: DUCKDB_TYPE_USMALLINT.rawValue)
/// Unsigned integer type castable to `UInt32`
static let uinteger = DatabaseType(rawValue: DUCKDB_TYPE_UINTEGER.rawValue)
/// Unsigned integer type castable to `UInt64`
static let ubigint = DatabaseType(rawValue: DUCKDB_TYPE_UBIGINT.rawValue)
/// Floating point type castable to `Float`
static let float = DatabaseType(rawValue: DUCKDB_TYPE_FLOAT.rawValue)
/// Floating point type castable to `Double`
static let double = DatabaseType(rawValue: DUCKDB_TYPE_DOUBLE.rawValue)
/// Timestamp type castable to `Timestamp`
static let timestamp = DatabaseType(rawValue: DUCKDB_TYPE_TIMESTAMP.rawValue)
/// Timestamp(TZ) type castable to `Timestamp`
static let timestampTz = DatabaseType(rawValue: DUCKDB_TYPE_TIMESTAMP_TZ.rawValue)
/// Date type castable to `Date`
static let date = DatabaseType(rawValue: DUCKDB_TYPE_DATE.rawValue)
/// Time type castable to `Time`
static let time = DatabaseType(rawValue: DUCKDB_TYPE_TIME.rawValue)
/// Time(TZ) type castable to `Time`
static let timeTz = DatabaseType(rawValue: DUCKDB_TYPE_TIME_TZ.rawValue)
/// Interval type castable to `Interval`
static let interval = DatabaseType(rawValue: DUCKDB_TYPE_INTERVAL.rawValue)
/// String type castable to `String`
static let varchar = DatabaseType(rawValue: DUCKDB_TYPE_VARCHAR.rawValue)
/// Data type castable to `Data`
static let blob = DatabaseType(rawValue: DUCKDB_TYPE_BLOB.rawValue)
/// Decimal type castable to `Decimal`
static let decimal = DatabaseType(rawValue: DUCKDB_TYPE_DECIMAL.rawValue)
/// Timestamp type castable to `Timestamp`
static let timestampS = DatabaseType(rawValue: DUCKDB_TYPE_TIMESTAMP_S.rawValue)
/// Timestamp type castable to `Timestamp`
static let timestampMS = DatabaseType(rawValue: DUCKDB_TYPE_TIMESTAMP_MS.rawValue)
/// Timestamp type castable to `Timestamp`
static let timestampNS = DatabaseType(rawValue: DUCKDB_TYPE_TIMESTAMP_NS.rawValue)
/// Enum type castable to suitable `Decodable` conforming types
static let `enum` = DatabaseType(rawValue: DUCKDB_TYPE_ENUM.rawValue)
/// Array type castable to suitable `Decodable` conforming types
static let list = DatabaseType(rawValue: DUCKDB_TYPE_LIST.rawValue)
/// Struct type castable to suitable `Decodable` conforming types
static let `struct` = DatabaseType(rawValue: DUCKDB_TYPE_STRUCT.rawValue)
/// Dictionary type castable to suitable `Decodable` conforming types
static let map = DatabaseType(rawValue: DUCKDB_TYPE_MAP.rawValue)
/// Enum type castable to suitable `Decodable` conforming types
static let union = DatabaseType(rawValue: DUCKDB_TYPE_UNION.rawValue)
/// UUID type castable to `UUID`
static let uuid = DatabaseType(rawValue: DUCKDB_TYPE_UUID.rawValue)
}
// MARK: - Internal Types
extension DatabaseType {
static let invalid = DatabaseType(rawValue: DUCKDB_TYPE_INVALID.rawValue)
}
extension DatabaseType: CustomStringConvertible {
public var description: String {
switch self {
case .boolean: return "\(Self.self).boolean"
case .tinyint: return "\(Self.self).tinyint"
case .smallint: return "\(Self.self).smallint"
case .integer: return "\(Self.self).integer"
case .bigint: return "\(Self.self).bigint"
case .utinyint: return "\(Self.self).utinyint"
case .usmallint: return "\(Self.self).usmallint"
case .uinteger: return "\(Self.self).uinteger"
case .ubigint: return "\(Self.self).ubigint"
case .float: return "\(Self.self).float"
case .double: return "\(Self.self).double"
case .timestamp: return "\(Self.self).timestamp"
case .timestampTz: return "\(Self.self).timestampTZ"
case .date: return "\(Self.self).date"
case .time: return "\(Self.self).time"
case .timeTz: return "\(Self.self).timeTZ"
case .interval: return "\(Self.self).interval"
case .hugeint: return "\(Self.self).hugeint"
case .uhugeint: return "\(Self.self).uhugeint"
case .varchar: return "\(Self.self).varchar"
case .blob: return "\(Self.self).blob"
case .decimal: return "\(Self.self).decimal"
case .timestampS: return "\(Self.self).timestampS"
case .timestampMS: return "\(Self.self).timestampMS"
case .timestampNS: return "\(Self.self).timestampNS"
case .`enum`: return "\(Self.self).enum"
case .list: return "\(Self.self).list"
case .`struct`: return "\(Self.self).struct"
case .map: return "\(Self.self).map"
case .union: return "\(Self.self).union"
case .uuid: return "\(Self.self).uuid"
case .invalid: return "\(Self.self).invalid"
default: return "\(Self.self).unknown - id: (\(self.rawValue))"
}
}
}

View File

@@ -0,0 +1,12 @@
# ``DuckDB``
DuckDB is an in-process SQL OLAP database management system
## Overview
DuckDB is a high-performance analytical database system. It is designed to be
fast, reliable and easy to use. DuckDB provides a rich SQL dialect, with
support far beyond basic SQL. DuckDB supports arbitrary and nested correlated
subqueries, window functions, collations, complex types (arrays, structs), and
more. For more information on the goals of DuckDB, please refer to the [Why
DuckDB page](https://duckdb.org/why_duckdb) on our website.

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.
#if canImport(TabularData)
import TabularData
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public extension TabularData.Column {
/// Creates a tabular data column initialized from a DuckDB column
init(_ column: DuckDB.Column<WrappedElement>) {
self.init(name: column.name, contents: column)
}
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public extension TabularData.AnyColumn {
/// Creates a type-erased tabular data column from a DuckDB column
init<T>(_ column: DuckDB.Column<T>) {
self = TabularData.Column(column).eraseToAnyColumn()
}
}
#endif

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
public extension Foundation.Date {
/// Creates a date value initialized from a DuckDB Date value
init(_ date: Date) {
self.init(timeIntervalSince1970: TimeInterval(date.days * 24 * 60 * 60))
}
}

View File

@@ -0,0 +1,73 @@
//
// 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
public extension Decimal {
init(_ source: IntHuge) {
let magnitude = source.magnitude
let mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
let significantBitCount = magnitude.bitWidth - magnitude.leadingZeroBitCount
let length = (significantBitCount + (UInt16.bitWidth - 1)) / UInt16.bitWidth
mantissa.0 = UInt16(truncatingIfNeeded: magnitude >> (0 * 16))
mantissa.1 = UInt16(truncatingIfNeeded: magnitude >> (1 * 16))
mantissa.2 = UInt16(truncatingIfNeeded: magnitude >> (2 * 16))
mantissa.3 = UInt16(truncatingIfNeeded: magnitude >> (3 * 16))
mantissa.4 = UInt16(truncatingIfNeeded: magnitude >> (4 * 16))
mantissa.5 = UInt16(truncatingIfNeeded: magnitude >> (5 * 16))
mantissa.6 = UInt16(truncatingIfNeeded: magnitude >> (6 * 16))
mantissa.7 = UInt16(truncatingIfNeeded: magnitude >> (7 * 16))
self = .init(
_exponent: 0,
_length: UInt32(length),
_isNegative: source.signum() < 0 ? 1 : 0,
_isCompact: 0,
_reserved: 0,
_mantissa: mantissa
)
}
init(_ source: UIntHuge) {
let mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
let significantBitCount = source.bitWidth - source.leadingZeroBitCount
let length = (significantBitCount + (UInt16.bitWidth - 1)) / UInt16.bitWidth
mantissa.0 = UInt16(truncatingIfNeeded: source >> (0 * 16))
mantissa.1 = UInt16(truncatingIfNeeded: source >> (1 * 16))
mantissa.2 = UInt16(truncatingIfNeeded: source >> (2 * 16))
mantissa.3 = UInt16(truncatingIfNeeded: source >> (3 * 16))
mantissa.4 = UInt16(truncatingIfNeeded: source >> (4 * 16))
mantissa.5 = UInt16(truncatingIfNeeded: source >> (5 * 16))
mantissa.6 = UInt16(truncatingIfNeeded: source >> (6 * 16))
mantissa.7 = UInt16(truncatingIfNeeded: source >> (7 * 16))
self = .init(
_exponent: 0,
_length: UInt32(length),
_isNegative: 0,
_isCompact: 0,
_reserved: 0,
_mantissa: mantissa
)
}
}

View File

@@ -0,0 +1,41 @@
//
// 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
public extension Foundation.Date {
/// Creates a date value initialized from a DuckDB Time value
init(_ date: Time) {
self.init(timeIntervalSince1970: TimeInterval(date.microseconds) * 1e-3)
}
}
public extension Time {
/// Creates a time value initialized from a Foundation Date
init(_ date: Foundation.Date) {
self.init(microseconds: Int64(date.timeIntervalSince1970 * 1e3))
}
}

View File

@@ -0,0 +1,41 @@
//
// 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
public extension Foundation.Date {
/// Creates a date value initialized from a DuckDB Timestamp value
init(_ timestamp: Timestamp) {
self.init(timeIntervalSince1970: TimeInterval(timestamp.microseconds) * 1e-3)
}
}
public extension Timestamp {
/// Creates a timestamp value initialized from a Foundation Date
init(_ date: Foundation.Date) {
self.init(microseconds: Int64(date.timeIntervalSince1970 * 1e3))
}
}

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)
}
}

View File

@@ -0,0 +1,163 @@
//
// 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
public final class LogicalType {
private let ptr = UnsafeMutablePointer<duckdb_logical_type?>.allocate(capacity: 1)
init(_ body: () -> duckdb_logical_type?) {
self.ptr.pointee = body()
}
deinit {
duckdb_destroy_logical_type(ptr)
ptr.deallocate()
}
/// The primitive type represented by this logical type.
public var dataType: DatabaseType {
let ctypeid = duckdb_get_type_id(ptr.pointee)
return ctypeid.asTypeID
}
/// The primitive type representing the internal storage type, which is equivalent
/// to ``dataType``, except for when the type is an enum.
public var underlyingDataType: DatabaseType {
guard dataType == .enum else { return dataType }
let ctypeid = duckdb_enum_internal_type(ptr.pointee)
return ctypeid.asTypeID
}
}
// MARK: - Decimal
public extension LogicalType {
/// Properties associated with a decimal type
struct DecimalProperties {
let width: UInt8
let scale: UInt8
let storageType: DatabaseType
}
/// Properties associated with a decimal type. For all other types, returns `nil`
var decimalProperties: DecimalProperties? {
guard dataType == .decimal else { return nil }
let internalStorageType = duckdb_decimal_internal_type(ptr.pointee)
return .init(
width: duckdb_decimal_width(ptr.pointee),
scale: duckdb_decimal_scale(ptr.pointee),
storageType: internalStorageType.asTypeID
)
}
}
// MARK: - Struct
public extension LogicalType {
static let structCompatibleTypes = [DatabaseType.struct, .map]
/// Properties associated with a struct type
struct StructMemberProperties {
let name: String
let type: LogicalType
}
/// Properties associated with a struct type. For all other types, returns `nil`
var structMemberProperties: [StructMemberProperties]? {
guard Self.structCompatibleTypes.contains(dataType) else { return nil }
let memberCount = duckdb_struct_type_child_count(ptr.pointee)
var properties = [StructMemberProperties]()
properties.reserveCapacity(Int(memberCount))
for i in 0..<memberCount {
let cStr = duckdb_struct_type_child_name(ptr.pointee, i)!
properties.append(StructMemberProperties(
name: String(cString: cStr),
type: LogicalType { duckdb_struct_type_child_type(ptr.pointee, i) }
))
duckdb_free(cStr)
}
return properties
}
}
// MARK: Union
public extension LogicalType {
/// Properties associated with a union type
struct UnionMemberProperties {
let name: String
let type: LogicalType
}
/// Properties associated with a union type. For all other types, returns `nil`
var unionMemberProperties: [UnionMemberProperties]? {
guard dataType == .union else { return nil }
let memberCount = duckdb_union_type_member_count(ptr.pointee)
var properties = [UnionMemberProperties]()
properties.reserveCapacity(Int(memberCount))
for i in 0..<memberCount {
let cStr = duckdb_union_type_member_name(ptr.pointee, i)!
properties.append(UnionMemberProperties(
name: String(cString: cStr),
type: LogicalType { duckdb_union_type_member_type(ptr.pointee, i) }
))
duckdb_free(cStr)
}
return properties
}
}
// MARK: - List
public extension LogicalType {
/// Child type of a list type. For all other types, returns `nil`
var listChildType: LogicalType? {
guard dataType == .list else { return nil }
return LogicalType { duckdb_list_type_child_type(ptr.pointee) }
}
}
// MARK: - Map
public extension LogicalType {
/// Key type of a map type. For all other types, returns `nil`
var mapKeyType: LogicalType? {
guard dataType == .map else { return nil }
return LogicalType { duckdb_map_type_key_type(ptr.pointee) }
}
/// Value type of a map type. For all other types, returns `nil`
var mapValueType: LogicalType? {
guard dataType == .map else { return nil }
return LogicalType { duckdb_map_type_value_type(ptr.pointee) }
}
}

View File

@@ -0,0 +1,458 @@
//
// 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
/// An object representing a DuckDB prepared statement
///
/// A prepared statement is a parameterized query. The query is prepared with
/// question marks (`?`) or dollar symbols (`$1`) indicating the parameters of
/// the query. Values can then be bound to these parameters, after which the
/// prepared statement can be executed using those parameters. A single query
/// can be prepared once and executed many times.
///
/// Prepared statements are useful to:
///
/// - Easily supply parameters to functions while avoiding string
/// concatenation/SQL injection attacks.
/// - Speed up queries that will be executed many times with different
/// parameters.
///
/// The following example creates a prepared statement that allows parameters
/// to be bound in two positions within a 'select' statement. The prepared
/// statement is finally executed by calling ``PreparedStatement/execute()``.
///
/// ```swift
/// let connection: Connection = ...
/// let statement = try PreparedStatement(
/// connection: connection,
/// query: "SELECT $1 from $2"
/// )
/// try statement.bind("last_name")
/// try statement.bind("employees")
/// // executes 'SELECT last_name from employees'
/// let result = try statement.execute()
/// ```
public final class PreparedStatement {
/// The number of parameters to which values can be bound
public var parameterCount: Int { Int(duckdb_nparams(ptr.pointee)) }
private let connection: Connection
private let ptr = UnsafeMutablePointer<duckdb_prepared_statement?>.allocate(capacity: 1)
/// Creates a new prepared statement for a given connection and query
///
/// The query is prepared with question marks (`?`) or dollar symbols (`$1`)
/// indicating the parameters of the query.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter connection: the connection on which the prepared stement will
/// execute
/// - Parameter query: the parameterized query
/// - Throws: ``DatabaseError/preparedStatementFailedToInitialize(reason:)``
/// if there is a problem with the query or connection
public init(connection: Connection, query: String) throws {
self.connection = connection
let status = query.withCString { queryStPtr in
connection.withCConnection { duckdb_prepare($0, queryStPtr, ptr) }
}
guard .success == status else {
throw DatabaseError.preparedStatementFailedToInitialize(reason: preparedStatementError())
}
}
deinit {
duckdb_destroy_prepare(ptr)
ptr.deallocate()
}
/// Executes the prepared statement
///
/// Issues the parameterized query to the database using the values previously
/// bound via the `bind(_:at:)` set of functions.
///
/// - Throws: ``DatabaseError/preparedStatementQueryError(reason:)``
public func execute() throws -> ResultSet {
try ResultSet(prepared: self)
}
func parameterType(at index: Int) -> DatabaseType {
let cparamtype = duckdb_param_type(ptr.pointee, DBInt(index))
return cparamtype.asTypeID
}
func clearBindings() {
duckdb_clear_bindings(ptr.pointee)
}
func withCPreparedStatement<T>(_ body: (duckdb_prepared_statement?) throws -> T) rethrows -> T {
try body(ptr.pointee)
}
}
public extension PreparedStatement {
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Bool?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_boolean(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Int8?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_int8(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Int16?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_int16(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Int32?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_int32(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Int64?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_int64(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: IntHuge?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_hugeint(ptr.pointee, .init(index), .init(value)) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: UIntHuge?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_uhugeint(ptr.pointee, .init(index), .init(value)) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Decimal?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_decimal(ptr.pointee, .init(index), try .init(value)) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: UInt8?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_uint8(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: UInt16?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_uint16(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: UInt32?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_uint32(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: UInt64?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_uint64(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Float?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_float(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Double?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_double(ptr.pointee, .init(index), value) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Date?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_date(ptr.pointee, .init(index), .init(date: value)) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Time?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand { duckdb_bind_time(ptr.pointee, .init(index), .init(time: value)) }
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Timestamp?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand {
duckdb_bind_timestamp(ptr.pointee, .init(index), .init(timestamp: value))
}
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Interval?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand {
duckdb_bind_interval(ptr.pointee, .init(index), .init(interval: value))
}
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: String?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
let data = value.data(using: .utf8)!
try withThrowingCommand {
data.withUnsafeBytes { dataPtr in
duckdb_bind_varchar_length(
ptr.pointee, .init(index), dataPtr.baseAddress, .init(dataPtr.count))
}
}
}
/// Binds a value of the given type at the specified parameter index
///
/// Sets the value that will be used for the next call to ``execute()``.
///
/// - Important: Prepared statement parameters use one-based indexing
/// - Parameter value: the value to bind
/// - Parameter index: the one-based parameter index
/// - Throws: ``DatabaseError/preparedStatementFailedToBindParameter(reason:)``
/// if there is a type-mismatch between the value being bound and the
/// underlying column type
func bind(_ value: Data?, at index: Int) throws {
guard let value = try unwrapValueOrBindNull(value, at: index) else { return }
try withThrowingCommand {
value.withUnsafeBytes { dataPtr in
duckdb_bind_blob(
ptr.pointee, .init(index), dataPtr.baseAddress, .init(dataPtr.count))
}
}
}
}
private extension PreparedStatement {
func bindNullValue(at index: Int) throws {
try withThrowingCommand { duckdb_bind_null(ptr.pointee, .init(index)) }
}
func unwrapValueOrBindNull<T>(_ value: T?, at index: Int) throws -> T? {
guard let value else {
try bindNullValue(at: index)
return nil
}
return value
}
func withThrowingCommand(_ body: () throws -> duckdb_state) throws {
let state = try body()
guard state == .success else {
throw DatabaseError.preparedStatementFailedToBindParameter(reason: preparedStatementError())
}
}
func preparedStatementError() -> String? {
duckdb_prepare_error(ptr.pointee).map(String.init(cString:))
}
}

View File

@@ -0,0 +1,251 @@
//
// 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
/// An object representing a DuckDB result set
///
/// A DuckDB result set contains the data returned from the database after a
/// successful query.
///
/// A result set is organized into vertical table slices called columns. Each
/// column of the result set is accessible by calling the ``subscript(_:)``
/// method of the result.
///
/// Elements of a column can be accessed by casting the column to the native
/// Swift type that matches the underlying database column type. See ``Column``
/// for further discussion.
public struct ResultSet: Sendable {
/// The number of chunks in the result set
public var chunkCount: DBInt { chunkStorage.chunkCount }
/// The number of columns in the result set
public var columnCount: DBInt { storage.columnCount }
/// The total number of rows in the result set
public var rowCount: DBInt { chunkStorage.totalRowCount }
private let storage: ResultStorage
private let chunkStorage: ChunkStorage
init(connection: Connection, sql: String) throws {
self.storage = try ResultStorage(connection: connection, sql: sql)
self.chunkStorage = ChunkStorage(resultStorage: storage)
}
init(prepared: PreparedStatement) throws {
self.storage = try ResultStorage(prepared: prepared)
self.chunkStorage = ChunkStorage(resultStorage: storage)
}
/// Returns a `Void` typed column for the given column index
///
/// A `Void` typed column can be cast to a column matching the underlying
/// database representation using ``Column/cast(to:)-4376d``. See ``Column``
/// for further discussion.
///
/// - Parameter columnIndex: the index of the column in the result set
/// - Returns: a `Void` typed column
public func column(at columnIndex: DBInt) -> Column<Void> {
precondition(columnIndex < columnCount)
return Column(result: self, columnIndex: columnIndex) { $0.unwrapNull() ? nil : () }
}
/// The underlying column name for the given column index
///
/// - Parameter columnIndex: the index of the column in the result set
/// - Returns: the name of the column
public func columnName(at columnIndex: DBInt) -> String {
storage.columnName(at: columnIndex)
}
/// The index of the given column name
///
/// - Parameter columnName: the name of the column in the result set
/// - Returns: the index of the column
/// - Complexity: O(n)
public func index(forColumnName columnName: String) -> DBInt? {
for i in 0..<columnCount {
if self.columnName(at: i) == columnName {
return i
}
}
return nil
}
func columnDataType(at index: DBInt) -> DatabaseType {
storage.columnDataType(at: index)
}
func columnLogicalType(at index: DBInt) -> LogicalType {
storage.columnLogicalType(at: index)
}
func element(forColumn columnIndex: DBInt, at index: DBInt) -> Vector.Element {
var chunkIndex = DBInt.zero
var chunkRowOffset = DBInt.zero
while chunkIndex < chunkCount {
let chunk = chunkStorage[Int(chunkIndex)]
let chunkCount = chunk.count
if index < chunkRowOffset + chunkCount {
return chunk.withVector(at: columnIndex) { vector in
vector[Int(index - chunkRowOffset)]
}
}
else {
chunkIndex += 1
chunkRowOffset += chunkCount
}
}
preconditionFailure("item out of bounds")
}
}
// MARK: - Collection conformance
extension ResultSet: RandomAccessCollection {
public typealias Element = Column<Void>
public struct Iterator: IteratorProtocol {
private let result: ResultSet
private var position: DBInt
init(result: ResultSet) {
self.result = result
self.position = result.startIndex
}
public mutating func next() -> Element? {
guard position < result.endIndex else { return nil }
defer { position += 1 }
return .some(result[position])
}
}
public var startIndex: DBInt { 0 }
public var endIndex: DBInt { columnCount }
public subscript(position: DBInt) -> Column<Void> {
column(at: position)
}
public func makeIterator() -> Iterator {
Iterator(result: self)
}
public func index(after i: DBInt) -> DBInt { i + 1 }
public func index(before i: DBInt) -> DBInt { i - 1 }
}
// MARK: - Debug Description
extension ResultSet: CustomDebugStringConvertible {
public var debugDescription: String {
let summary = "chunks: \(chunkCount); rows: \(rowCount); columns: \(columnCount); layout:"
var columns = [String]()
for i in 0..<columnCount {
let name = columnName(at: i)
let type = columnDataType(at: i).description.uppercased()
columns.append("\t\(name) \(type)")
}
return "<\(Self.self): { \(summary) (\n\(columns.joined(separator: ",\n"))\n);>"
}
}
// MARK: - Utilities
fileprivate final class ResultStorage: Sendable {
let columnCount: DBInt
private let ptr = UnsafeMutablePointer<duckdb_result>.allocate(capacity: 1)
init(connection: Connection, sql: String) throws {
let status = sql.withCString { [ptr] queryStrPtr in
connection.withCConnection { duckdb_query($0, queryStrPtr, ptr) }
}
guard status == .success else {
let error = duckdb_result_error(ptr).map(String.init(cString:))
throw DatabaseError.connectionQueryError(reason: error)
}
self.columnCount = duckdb_column_count(ptr)
}
init(prepared: PreparedStatement) throws {
let status = prepared.withCPreparedStatement { [ptr] in duckdb_execute_prepared($0, ptr) }
guard status == .success else {
let error = duckdb_result_error(ptr).map(String.init(cString:))
throw DatabaseError.preparedStatementQueryError(reason: error)
}
self.columnCount = duckdb_column_count(ptr)
}
func columnName(at columnIndex: DBInt) -> String {
String(cString: duckdb_column_name(ptr, columnIndex))
}
func columnDataType(at index: DBInt) -> DatabaseType {
let dataType = duckdb_column_type(ptr, index)
return DatabaseType(rawValue: dataType.rawValue)
}
func columnLogicalType(at index: DBInt) -> LogicalType {
return LogicalType { duckdb_column_logical_type(ptr, index) }
}
func withCResult<T>(_ body: (duckdb_result) throws -> T) rethrows -> T {
try body(ptr.pointee)
}
deinit {
duckdb_destroy_result(ptr)
ptr.deallocate()
}
}
fileprivate final class ChunkStorage: Sendable {
let chunkCount: DBInt
let totalRowCount: DBInt
private let chunks: [DataChunk]
init(resultStorage: ResultStorage) {
let chunkCount = resultStorage.withCResult { duckdb_result_chunk_count($0) }
var chunks = [DataChunk]()
var totalRowCount = DBInt(0)
for i in 0 ..< chunkCount {
let chunk = resultStorage.withCResult { DataChunk(cresult: $0, index: i) }
chunks.append(chunk)
totalRowCount += chunk.count
}
self.chunks = chunks
self.chunkCount = chunkCount
self.totalRowCount = totalRowCount
}
subscript(position: Int) -> DataChunk { chunks[position] }
}

View File

@@ -0,0 +1,69 @@
//
// 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
/// A date in the Gregorian calendar
///
/// A date specifies a combination of year, month and day. DuckDB follows the
/// SQL standards lead by counting dates exclusively in the Gregorian calendar,
/// even for years before that calendar was in use.
public struct Date: Hashable, Equatable, Codable, Sendable {
/// days since the unix date epoch `1970-01-01`
public var days: Int32
}
public extension Date {
/// The components of ``Date`` decomposed into its constituent parts
///
/// A type to facilate the conversion between nominal units of years, months
/// and days into the underlying DuckDB ``Date`` representation of days since
/// `1970-01-01`.
struct Components: Hashable, Equatable {
public var year: Int32
public var month: Int8
public var day: Int8
}
/// Creates a new instance from the given date components
///
/// - Parameter components: the date components of the instance
init(components: Components) {
let cdatestruct = duckdb_to_date(duckdb_date_struct(components: components))
self = cdatestruct.asDate
}
/// Date components
var components: Components { Components(self) }
}
private extension Date.Components {
init(_ date: Date) {
let cdate = duckdb_date(date: date)
let cdatestruct = duckdb_from_date(cdate)
self = cdatestruct.asDateComponents
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
//
// 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
/// A period of time
///
/// Intervals represent a period of time. This period can be measured in a
/// variety of units, for example years, days, or seconds.
public struct Interval: Hashable, Equatable, Sendable {
public var months: Int32
public var days: Int32
public var microseconds: Int64
}
extension Interval {
init(
years: Int32,
months: Int32,
days: Int32,
hours: Int32,
minutes: Int32,
seconds: Int32,
microseconds: Int64
) {
let hours_ms = Int64(hours) * 60 * 60 * 1_000_000
let minutes_ms = Int64(minutes) * 60 * 1_000_000
let seconds_ms = Int64(seconds) * 1_000_000
self.init(
months: (years * 12) + months,
days: days,
microseconds: hours_ms + minutes_ms + seconds_ms + microseconds
)
}
}

View File

@@ -0,0 +1,76 @@
//
// 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
/// A point in absolute time
///
/// Time represents points in absolute time, usually called instants.
/// DuckDB represents instants as the number of microseconds (µs) since
/// `1970-01-01 00:00:00+00`.
public struct Time: Hashable, Equatable, Sendable {
/// microseconds (µs) since `1970-01-01 00:00:00+00`.
public var microseconds: Int64
}
public extension Time {
/// The components of ``Time`` decomposed into its constituent parts
///
/// A type to facilate the conversion between nominal units of hours,
/// minutes, seconds and microseconds into the underlying DuckDB
/// ``Time`` representation of microseconds (µs) since
/// `1970-01-01 00:00:00+00`.
struct Components: Hashable, Equatable {
public var hour: Int8
public var minute: Int8
public var second: Int8
public var microsecond: Int32
}
/// Creates a new instance from the given time components
///
/// - Parameter components: the time components of the instance
init(components: Components) {
let ctimestruct = duckdb_to_time(duckdb_time_struct(components: components))
self = ctimestruct.asTime
}
/// Time components
var components: Components { Components(self) }
}
private extension Time.Components {
init(_ time: Time) {
let ctime = duckdb_time(time: time)
let ctimestruct = duckdb_from_time(ctime)
self = ctimestruct.asTimeComponents
}
}
public struct TimeTz: Hashable, Equatable, Sendable {
public var time: Time
public var offset: Int32
}

View File

@@ -0,0 +1,87 @@
//
// 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
/// A point in absolute time
///
/// Timestamps represent points in absolute time, usually called instants.
/// DuckDB represents instants as the number of microseconds (µs) since
/// `1970-01-01 00:00:00+00`.
public struct Timestamp: Hashable, Equatable, Codable, Sendable {
/// microseconds (µs) since `1970-01-01 00:00:00+00`.
public var microseconds: Int64
}
public extension Timestamp {
/// The components of a ``Timestamp`` decomposed into its constituent parts
///
/// A type to facilate the conversion between nominal units of year, month,
/// day, hours, minutes, seconds and microseconds into the underlying DuckDB
/// ``Timestamp`` representation of microseconds (µs) since
/// `1970-01-01 00:00:00+00`.
struct Components: Hashable, Equatable {
/// Date components
public var date: Date.Components
/// Time components
public var time: Time.Components
}
/// Creates a new instance from the given timestamp components
///
/// - Parameter components: the components of the timestamp to be instantiated
init(components: Components) {
let ctimestampstruct = duckdb_to_timestamp(duckdb_timestamp_struct(components: components))
self = ctimestampstruct.asTimestamp
}
/// Timestamp components
var components: Components { Components(self) }
}
private extension Timestamp.Components {
init(_ timestamp: Timestamp) {
let ctimestamp = duckdb_timestamp(timestamp: timestamp)
let ctimestampstruct = duckdb_from_timestamp(ctimestamp)
self = ctimestampstruct.asTimestampComponents
}
}
extension Timestamp.Components {
init(
year: Int32,
month: Int8,
day: Int8,
hour: Int8,
minute: Int8,
second: Int8,
microsecond: Int32
) {
self.date = .init(year: year, month: month, day: day)
self.time = .init(hour: hour, minute: minute, second: second, microsecond: microsecond)
}
}