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