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,242 @@
//
// 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
import XCTest
@testable import DuckDB
final class AppenderTests: XCTestCase {
func test_bool_round_trip() throws {
try roundTripTest(
dataType: "BOOL",
expected: [false, true, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Bool.self) }
)
}
func test_utinyint_round_trip() throws {
try roundTripTest(
dataType: "UTINYINT",
expected: [UInt8.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: UInt8.self) }
)
}
func test_usmallint_round_trip() throws {
try roundTripTest(
dataType: "USMALLINT",
expected: [UInt16.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: UInt16.self) }
)
}
func test_uint_round_trip() throws {
try roundTripTest(
dataType: "UINTEGER",
expected: [UInt32.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: UInt32.self) }
)
}
func test_ubigint_round_trip() throws {
try roundTripTest(
dataType: "UBIGINT",
expected: [UInt64.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: UInt64.self) }
)
}
func test_tinyint_round_trip() throws {
try roundTripTest(
dataType: "TINYINT",
expected: [Int8.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Int8.self) }
)
}
func test_smallint_round_trip() throws {
try roundTripTest(
dataType: "SMALLINT",
expected: [Int16.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Int16.self) }
)
}
func test_int_round_trip() throws {
try roundTripTest(
dataType: "INTEGER",
expected: [Int32.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Int32.self) }
)
}
func test_bigint_round_trip() throws {
try roundTripTest(
dataType: "BIGINT",
expected: [Int64.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Int64.self) }
)
}
func test_hugeint_round_trip() throws {
try roundTripTest(
dataType: "HUGEINT",
expected: [IntHuge.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: IntHuge.self) }
)
}
func test_uhugeint_round_trip() throws {
try roundTripTest(
dataType: "UHUGEINT",
expected: [UIntHuge.min, .max, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: UIntHuge.self) }
)
}
func test_float_round_trip() throws {
try roundTripTest(
dataType: "FLOAT",
expected: [-Float.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Float.self) }
)
}
func test_double_round_trip() throws {
try roundTripTest(
dataType: "DOUBLE",
expected: [-Double.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Double.self) }
)
}
func test_varchar_round_trip() throws {
try roundTripTest(
dataType: "VARCHAR",
expected: ["🦆🦆🦆🦆🦆🦆", "goo\0se", nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: String.self) }
)
}
func test_time_round_trip() throws {
let t1 = Time.Components(hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Time.Components(hour: 23, minute: 59, second: 59, microsecond: 999_999)
try roundTripTest(
dataType: "TIME",
expected: [Time(components: t1), Time(components: t2), nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Time.self) }
)
}
func test_date_round_trip() throws {
let d1 = Date.Components(year: -5_877_641, month: 06, day: 25)
let d2 = Date.Components(year: 5_881_580, month: 07, day: 10)
try roundTripTest(
dataType: "DATE",
expected: [Date(components: d1), Date(components: d2), nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Date.self) }
)
}
func test_timestamp_round_trip() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 775_806)
try roundTripTest(
dataType: "TIMESTAMP",
expected: [Timestamp(components: t1), Timestamp(components: t2), nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Timestamp.self) }
)
}
func test_interval_round_trip() throws {
let t1 = Interval(
years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0, microseconds: 0)
let t2 = Interval(
years: 83, months: 3, days: 999, hours: 0, minutes: 16, seconds: 39, microseconds: 999_999)
try roundTripTest(
dataType: "INTERVAL",
expected: [t1, t2, nil],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Interval.self) }
)
}
func test_blob_round_trip() throws {
try roundTripTest(
dataType: "BLOB",
expected: [
"thisisalongblob\0withnullbytes".data(using: .ascii)!,
"\0\0\0a".data(using: .ascii)!,
nil
],
append: { appender, item in try appender.append(item) },
cast: { $0.cast(to: Data.self) }
)
}
}
private extension AppenderTests {
func roundTripTest<T: Equatable>(
dataType: String,
expected: [T?],
append: (Appender, T?) throws -> Void,
cast: (Column<Void>) -> Column<T>
) throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("CREATE TABLE t1(i \(dataType));")
let appender = try Appender(connection: connection, table: "t1")
for item in expected {
try append(appender, item)
try appender.endRow()
}
try appender.flush()
let result = try connection.query("SELECT * FROM t1;")
let column = cast(result[0])
for (index, item) in expected.enumerated() {
XCTAssertEqual(column[DBInt(index)], item)
}
}
}

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.
import XCTest
@testable import DuckDB
final class CodingUserInfoKeysTests: XCTestCase {
func test_logical_type() throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("""
CREATE TABLE t1(int_list INT[]);
INSERT INTO t1 VALUES ([1, 2, 3]);
""")
let result = try connection.query("SELECT * FROM t1;")
let column = result[0].cast(to: TestDynamicDecodable.self)
XCTAssertEqual(column[0]?.logicalType?.dataType, .list)
}
}
struct TestDynamicDecodable: Decodable {
let logicalType: LogicalType?
init(from decoder: Decoder) throws {
self.logicalType = decoder.userInfo[CodingUserInfoKeys.logicalTypeCodingUserInfoKey] as? LogicalType
}
}

View File

@@ -0,0 +1,85 @@
//
// 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 XCTest
@testable import DuckDB
final class DatabaseTests: XCTestCase {
func test_create_in_memory_database() throws {
let _ = try Database(store: .inMemory)
}
func test_create_local_file_datebase() throws {
let fileURL = try Self.generateTemporaryFileURL(forFileNamed: "test.tb")
defer { Self.cleanUpTemporaryFileURL(fileURL) }
let _ = try Database(store: .file(at: fileURL))
}
func test_connect_to_in_memory_datebase() throws {
let _ = try Database(store: .inMemory).connect()
}
func test_connect_to_local_file_datebase() throws {
let fileURL = try Self.generateTemporaryFileURL(forFileNamed: "test.tb")
defer { Self.cleanUpTemporaryFileURL(fileURL) }
let _ = try Database(store: .file(at: fileURL)).connect()
}
func test_execute_statement() throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("SELECT version();")
}
func test_query_result() throws {
let connection = try Database(store: .inMemory).connect()
let result = try connection.query("SELECT * FROM test_all_types();")
let element: Void? = result[0][0]
XCTAssertNotNil(element)
}
}
// MARK: - Temp Directory Helpers
private extension DatabaseTests {
static func generateTemporaryFileURL(forFileNamed fileName: String) throws -> URL {
let tmpDirURL = try FileManager.default.url(
for: .itemReplacementDirectory,
in: .userDomainMask,
appropriateFor: FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0],
create: true
)
return tmpDirURL.appendingPathComponent(fileName)
}
static func cleanUpTemporaryFileURL(_ fileURL: URL) {
do {
try FileManager.default.removeItem(at: fileURL.deletingLastPathComponent())
}
catch {
print("Ignored failed attempt to remove temp dir at \(fileURL):\n\t\(error)")
}
}
}

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.
import Foundation
import XCTest
@testable import DuckDB
final class DecimalUtilityTests: XCTestCase {
func test_decimal_int_huge_min() throws {
let minInt128 = Decimal(string: "\(IntHuge.min)")!
XCTAssertEqual(Decimal(IntHuge.min), minInt128)
}
func test_decimal_int_huge_max() throws {
let maxInt128 = Decimal(string: "\(IntHuge.max)")!
XCTAssertEqual(Decimal(IntHuge.max), maxInt128)
}
func test_decimal_uint_huge_min() throws {
let minUInt128 = Decimal(string: "\(UIntHuge.min)")!
XCTAssertEqual(Decimal(UIntHuge.min), minUInt128)
}
func test_decimal_uint_huge_max() throws {
let maxUInt128 = Decimal(string: "\(UIntHuge.max)")!
XCTAssertEqual(Decimal(UIntHuge.max), maxUInt128)
}
}

View File

@@ -0,0 +1,60 @@
//
// 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
import XCTest
@testable import DuckDB
final class ExtensionTests: XCTestCase {
func test_parquet() throws {
try loadExtension(extension_name: "parquet")
}
func test_json() throws {
try loadExtension(extension_name: "json")
}
func test_icu() throws {
try loadExtension(extension_name: "icu")
}
}
private extension ExtensionTests {
func loadExtension(extension_name: String) throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("LOAD \(extension_name)")
let prepped = try PreparedStatement(
connection: connection,
query: "SELECT installed, loaded FROM duckdb_extensions() where extension_name = $1"
)
try prepped.bind(extension_name, at: 1);
let result = try prepped.execute()
XCTAssertEqual(result[0].cast(to: Bool.self)[0], true)
XCTAssertEqual(result[1].cast(to: Bool.self)[0], true)
}
}

View File

@@ -0,0 +1,405 @@
//
// 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
import XCTest
@testable import DuckDB
final class LogicalTypeTests: XCTestCase {
func test_boolean() throws {
try logicalTypeTest(
dataType: "BOOL",
cast: { $0.cast(to: Bool.self) },
validate: {
XCTAssertEqual($0.dataType, .boolean)
XCTAssertEqual($0.underlyingDataType, .boolean)
}
)
}
func test_tinyint() throws {
try logicalTypeTest(
dataType: "TINYINT",
cast: { $0.cast(to: Int8.self) },
validate: {
XCTAssertEqual($0.dataType, .tinyint)
XCTAssertEqual($0.underlyingDataType, .tinyint)
}
)
}
func test_smallint() throws {
try logicalTypeTest(
dataType: "SMALLINT",
cast: { $0.cast(to: Int16.self) },
validate: {
XCTAssertEqual($0.dataType, .smallint)
XCTAssertEqual($0.underlyingDataType, .smallint)
}
)
}
func test_integer() throws {
try logicalTypeTest(
dataType: "INTEGER",
cast: { $0.cast(to: Int32.self) },
validate: {
XCTAssertEqual($0.dataType, .integer)
XCTAssertEqual($0.underlyingDataType, .integer)
}
)
}
func test_bigint() throws {
try logicalTypeTest(
dataType: "BIGINT",
cast: { $0.cast(to: Int64.self) },
validate: {
XCTAssertEqual($0.dataType, .bigint)
XCTAssertEqual($0.underlyingDataType, .bigint)
}
)
}
func test_hugeint() throws {
try logicalTypeTest(
dataType: "HUGEINT",
cast: { $0.cast(to: IntHuge.self) },
validate: {
XCTAssertEqual($0.dataType, .hugeint)
XCTAssertEqual($0.underlyingDataType, .hugeint)
}
)
}
func test_uhugeint() throws {
try logicalTypeTest(
dataType: "UHUGEINT",
cast: { $0.cast(to: UIntHuge.self) },
validate: {
XCTAssertEqual($0.dataType, .uhugeint)
XCTAssertEqual($0.underlyingDataType, .uhugeint)
}
)
}
func test_utinyint() throws {
try logicalTypeTest(
dataType: "UTINYINT",
cast: { $0.cast(to: UInt8.self) },
validate: {
XCTAssertEqual($0.dataType, .utinyint)
XCTAssertEqual($0.underlyingDataType, .utinyint)
}
)
}
func test_usmallint() throws {
try logicalTypeTest(
dataType: "USMALLINT",
cast: { $0.cast(to: UInt16.self) },
validate: {
XCTAssertEqual($0.dataType, .usmallint)
XCTAssertEqual($0.underlyingDataType, .usmallint)
}
)
}
func test_uinteger() throws {
try logicalTypeTest(
dataType: "UINTEGER",
cast: { $0.cast(to: UInt32.self) },
validate: {
XCTAssertEqual($0.dataType, .uinteger)
XCTAssertEqual($0.underlyingDataType, .uinteger)
}
)
}
func test_ubigint() throws {
try logicalTypeTest(
dataType: "UBIGINT",
cast: { $0.cast(to: UInt64.self) },
validate: {
XCTAssertEqual($0.dataType, .ubigint)
XCTAssertEqual($0.underlyingDataType, .ubigint)
}
)
}
func test_float() throws {
try logicalTypeTest(
dataType: "FLOAT",
cast: { $0.cast(to: Float.self) },
validate: {
XCTAssertEqual($0.dataType, .float)
XCTAssertEqual($0.underlyingDataType, .float)
}
)
}
func test_double() throws {
try logicalTypeTest(
dataType: "DOUBLE",
cast: { $0.cast(to: Double.self) },
validate: {
XCTAssertEqual($0.dataType, .double)
XCTAssertEqual($0.underlyingDataType, .double)
}
)
}
func test_timestamp() throws {
try logicalTypeTest(
dataType: "TIMESTAMP",
cast: { $0.cast(to: Timestamp.self) },
validate: {
XCTAssertEqual($0.dataType, .timestamp)
XCTAssertEqual($0.underlyingDataType, .timestamp)
}
)
}
func test_timestamp_s() throws {
try logicalTypeTest(
dataType: "TIMESTAMP_S",
cast: { $0.cast(to: Timestamp.self) },
validate: {
XCTAssertEqual($0.dataType, .timestampS)
XCTAssertEqual($0.underlyingDataType, .timestampS)
}
)
}
func test_timestamp_ms() throws {
try logicalTypeTest(
dataType: "TIMESTAMP_MS",
cast: { $0.cast(to: Timestamp.self) },
validate: {
XCTAssertEqual($0.dataType, .timestampMS)
XCTAssertEqual($0.underlyingDataType, .timestampMS)
}
)
}
func test_timestamp_ns() throws {
try logicalTypeTest(
dataType: "TIMESTAMP_NS",
cast: { $0.cast(to: Timestamp.self) },
validate: {
XCTAssertEqual($0.dataType, .timestampNS)
XCTAssertEqual($0.underlyingDataType, .timestampNS)
}
)
}
func test_date() throws {
try logicalTypeTest(
dataType: "DATE",
cast: { $0.cast(to: Date.self) },
validate: {
XCTAssertEqual($0.dataType, .date)
XCTAssertEqual($0.underlyingDataType, .date)
}
)
}
func test_time() throws {
try logicalTypeTest(
dataType: "TIME",
cast: { $0.cast(to: Time.self) },
validate: {
XCTAssertEqual($0.dataType, .time)
XCTAssertEqual($0.underlyingDataType, .time)
}
)
}
func test_interval() throws {
try logicalTypeTest(
dataType: "INTERVAL",
cast: { $0.cast(to: Interval.self) },
validate: {
XCTAssertEqual($0.dataType, .interval)
XCTAssertEqual($0.underlyingDataType, .interval)
}
)
}
func test_varchar() throws {
try logicalTypeTest(
dataType: "VARCHAR",
cast: { $0.cast(to: String.self) },
validate: {
XCTAssertEqual($0.dataType, .varchar)
XCTAssertEqual($0.underlyingDataType, .varchar)
}
)
}
func test_blob() throws {
try logicalTypeTest(
dataType: "BLOB",
cast: { $0.cast(to: Data.self) },
validate: {
XCTAssertEqual($0.dataType, .blob)
XCTAssertEqual($0.underlyingDataType, .blob)
}
)
}
func test_decimal() throws {
try logicalTypeTest(
dataType: "DECIMAL(38, 10)",
cast: { $0.cast(to: Decimal.self) },
validate: {
XCTAssertEqual($0.dataType, .decimal)
XCTAssertEqual($0.underlyingDataType, .decimal)
let properties = $0.decimalProperties!
XCTAssertEqual(properties.scale, 10)
XCTAssertEqual(properties.width, 38)
XCTAssertEqual(properties.storageType, .hugeint)
}
)
}
enum Mood: String, Equatable, Decodable {
case sad
case ok
case happy
}
func test_enum() throws {
try logicalTypeTest(
dataType: "mood",
cast: { $0.cast(to: Mood.self) },
before: { connection in
try connection.execute("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');")
},
validate: {
XCTAssertEqual($0.dataType, .enum)
XCTAssertEqual($0.underlyingDataType, .utinyint)
}
)
}
func test_list() throws {
try logicalTypeTest(
dataType: "INT[]",
cast: { $0.cast(to: [Int32?].self) },
validate: {
XCTAssertEqual($0.dataType, .list)
XCTAssertEqual($0.underlyingDataType, .list)
XCTAssertEqual($0.listChildType?.dataType, .integer)
}
)
}
struct NumStrStruct: Equatable, Decodable {
let num: Int32
let str: String
}
func test_struct() throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("CREATE TABLE t1(num INTEGER, str VARCHAR);")
let result = try connection.query("SELECT {'num': num, 'str': str} as struct_column FROM t1;")
let logicalType = result[0].cast(to: NumStrStruct.self).underlyingLogicalType
XCTAssertEqual(logicalType.dataType, .struct)
XCTAssertEqual(logicalType.underlyingDataType, .struct)
let properties = logicalType.structMemberProperties!
XCTAssertEqual(properties.count, 2)
XCTAssertEqual(properties[0].name, "num")
XCTAssertEqual(properties[0].type.dataType, .integer)
XCTAssertEqual(properties[1].name, "str")
XCTAssertEqual(properties[1].type.dataType, .varchar)
}
func test_map() throws {
try logicalTypeTest(
dataType: "MAP(INT,DOUBLE)",
cast: { $0.cast(to: [Int32: Double].self) },
validate: {
XCTAssertEqual($0.dataType, .map)
XCTAssertEqual($0.underlyingDataType, .map)
XCTAssertEqual($0.mapKeyType?.dataType, .integer)
XCTAssertEqual($0.mapValueType?.dataType, .double)
}
)
}
enum NumStrUnion: Equatable, Decodable {
case num(Int32)
case str(String)
}
func test_union() throws {
try logicalTypeTest(
dataType: "UNION(num INT, str VARCHAR)",
cast: { $0.cast(to: NumStrUnion.self) },
validate: {
XCTAssertEqual($0.dataType, .union)
XCTAssertEqual($0.underlyingDataType, .union)
let properties = $0.unionMemberProperties!
XCTAssertEqual(properties.count, 2)
XCTAssertEqual(properties[0].name, "num")
XCTAssertEqual(properties[0].type.dataType, .integer)
XCTAssertEqual(properties[1].name, "str")
XCTAssertEqual(properties[1].type.dataType, .varchar)
}
)
}
func test_uuid() throws {
try logicalTypeTest(
dataType: "UUID",
cast: { $0.cast(to: UUID.self) },
validate: {
XCTAssertEqual($0.dataType, .uuid)
XCTAssertEqual($0.underlyingDataType, .uuid)
}
)
}
}
private extension LogicalTypeTests {
func logicalTypeTest<T: Equatable>(
dataType: String,
cast: (Column<Void>) -> Column<T>,
before: ((Connection) throws -> Void)? = nil,
validate: (LogicalType) -> Void
) throws {
let connection = try Database(store: .inMemory).connect()
try before?(connection)
try connection.execute("CREATE TABLE t1(i \(dataType));")
let result = try connection.query("SELECT * FROM t1;")
validate(cast(result[0]).underlyingLogicalType)
}
}

View File

@@ -0,0 +1,294 @@
//
// 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
import XCTest
@testable import DuckDB
final class PreparedStatementTests: XCTestCase {
func test_bool_round_trip() throws {
try roundTripTest(
dataType: "BOOL",
expected: [false, true, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Bool.self) }
)
}
func test_utinyint_round_trip() throws {
try roundTripTest(
dataType: "UTINYINT",
expected: [UInt8.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: UInt8.self) }
)
}
func test_usmallint_round_trip() throws {
try roundTripTest(
dataType: "USMALLINT",
expected: [UInt16.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: UInt16.self) }
)
}
func test_uint_round_trip() throws {
try roundTripTest(
dataType: "UINTEGER",
expected: [UInt32.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: UInt32.self) }
)
}
func test_ubigint_round_trip() throws {
try roundTripTest(
dataType: "UBIGINT",
expected: [UInt64.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: UInt64.self) }
)
}
func test_tinyint_round_trip() throws {
try roundTripTest(
dataType: "TINYINT",
expected: [Int8.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Int8.self) }
)
}
func test_smallint_round_trip() throws {
try roundTripTest(
dataType: "SMALLINT",
expected: [Int16.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Int16.self) }
)
}
func test_int_round_trip() throws {
try roundTripTest(
dataType: "INTEGER",
expected: [Int32.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Int32.self) }
)
}
func test_bigint_round_trip() throws {
try roundTripTest(
dataType: "BIGINT",
expected: [Int64.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Int64.self) }
)
}
func test_hugeint_round_trip() throws {
try roundTripTest(
dataType: "HUGEINT",
expected: [IntHuge.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: IntHuge.self) }
)
}
func test_uhugeint_round_trip() throws {
try roundTripTest(
dataType: "UHUGEINT",
expected: [UIntHuge.min, .max, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: UIntHuge.self) }
)
}
func test_float_round_trip() throws {
try roundTripTest(
dataType: "FLOAT",
expected: [-Float.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Float.self) }
)
}
func test_double_round_trip() throws {
try roundTripTest(
dataType: "DOUBLE",
expected: [-Double.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Double.self) }
)
}
func test_varchar_round_trip() throws {
try roundTripTest(
dataType: "VARCHAR",
expected: ["🦆🦆🦆🦆🦆🦆", "goo\0se", nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: String.self) }
)
}
func test_time_round_trip() throws {
let t1 = Time.Components(hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Time.Components(hour: 23, minute: 59, second: 59, microsecond: 999_999)
try roundTripTest(
dataType: "TIME",
expected: [Time(components: t1), Time(components: t2), nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Time.self) }
)
}
func test_date_round_trip() throws {
let d1 = Date.Components(year: -5_877_641, month: 06, day: 25)
let d2 = Date.Components(year: 5_881_580, month: 07, day: 10)
try roundTripTest(
dataType: "DATE",
expected: [Date(components: d1), Date(components: d2), nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Date.self) }
)
}
func test_timestamp_round_trip() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 775_806)
try roundTripTest(
dataType: "TIMESTAMP",
expected: [Timestamp(components: t1), Timestamp(components: t2), nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Timestamp.self) }
)
}
func test_interval_round_trip() throws {
let t1 = Interval(
years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0, microseconds: 0)
let t2 = Interval(
years: 83, months: 3, days: 999, hours: 0, minutes: 16, seconds: 39, microseconds: 999_999)
try roundTripTest(
dataType: "INTERVAL",
expected: [t1, t2, nil],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Interval.self) }
)
}
func test_blob_round_trip() throws {
try roundTripTest(
dataType: "BLOB",
expected: [
"thisisalongblob\0withnullbytes".data(using: .ascii)!,
"\0\0\0a".data(using: .ascii)!,
nil
],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Data.self) }
)
}
func test_decimal_4_1_roundtrip() throws {
try roundTripTest(
dataType: "DECIMAL(4,1)",
expected: [
Decimal(string: "-999.9"),
Decimal(string: " 999.9"),
nil
],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Decimal.self) }
)
}
func test_decimal_9_4_roundtrip() throws {
try roundTripTest(
dataType: "DECIMAL(9,4)",
expected: [
Decimal(string: "-99999.9999"),
Decimal(string: " 99999.9999"),
nil
],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Decimal.self) }
)
}
func test_decimal_18_6_roundtrip() throws {
try roundTripTest(
dataType: "DECIMAL(18,6)",
expected: [
Decimal(string: "-999999999999.999999"),
Decimal(string: "999999999999.999999"),
nil
],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Decimal.self) }
)
}
func test_decimal_38_10_roundtrip() throws {
try roundTripTest(
dataType: "DECIMAL(38,10)",
expected: [
Decimal(string: "-9999999999999999999999999999.9999999999"),
Decimal(string: "9999999999999999999999999999.9999999999"),
nil
],
bind: { statement, item in try statement.bind(item, at: 1) },
cast: { $0.cast(to: Decimal.self) }
)
}
}
private extension PreparedStatementTests {
func roundTripTest<T: Equatable>(
dataType: String,
expected: [T?],
bind: (PreparedStatement, T?) throws -> Void,
cast: (Column<Void>) -> Column<T>
) throws {
let connection = try Database(store: .inMemory).connect()
try connection.execute("CREATE TABLE t1(i \(dataType));")
let statement = try PreparedStatement(
connection: connection, query: "INSERT INTO t1 VALUES ($1);")
for item in expected {
try bind(statement, item)
let _ = try statement.execute()
}
let result = try connection.query("SELECT * FROM t1;")
let column = cast(result[0])
for (index, item) in expected.enumerated() {
XCTAssertEqual(column[DBInt(index)], item)
}
}
}

View File

@@ -0,0 +1,440 @@
//
// 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
import XCTest
@testable import DuckDB
final class TypeConversionTests: XCTestCase {
func test_extract_from_bool() throws {
try extractTest(
testColumnName: "bool", expected: [false, true, nil]) { $0.cast(to: Bool.self) }
}
func test_extract_from_utinyint() throws {
try extractTest(
testColumnName: "utinyint", expected: [UInt8.min, .max, nil]) { $0.cast(to: UInt8.self) }
}
func test_extract_from_usmallint() throws {
try extractTest(
testColumnName: "usmallint", expected: [UInt16.min, .max, nil]) { $0.cast(to: UInt16.self) }
}
func test_extract_from_uint() throws {
try extractTest(
testColumnName: "uint", expected: [UInt32.min, .max, nil]) { $0.cast(to: UInt32.self) }
}
func test_extract_from_ubigint() throws {
try extractTest(
testColumnName: "ubigint", expected: [UInt64.min, .max, nil]) { $0.cast(to: UInt64.self) }
}
func test_extract_from_tinyint() throws {
try extractTest(
testColumnName: "tinyint", expected: [Int8.min, .max, nil]) { $0.cast(to: Int8.self) }
}
func test_extract_from_smallint() throws {
try extractTest(
testColumnName: "smallint", expected: [Int16.min, .max, nil]) { $0.cast(to: Int16.self) }
}
func test_extract_from_int() throws {
try extractTest(
testColumnName: "int", expected: [Int32.min, .max, nil]) { $0.cast(to: Int32.self) }
}
func test_extract_from_bigint() throws {
try extractTest(
testColumnName: "bigint", expected: [Int64.min, .max, nil]) { $0.cast(to: Int64.self) }
}
func test_extract_from_hugeint() throws {
let expected = [IntHuge.min, IntHuge.max, nil]
try extractTest(testColumnName: "hugeint", expected: expected) { $0.cast(to: IntHuge.self) }
}
func test_extract_from_uhugeint() throws {
let expected = [UIntHuge.min, UIntHuge.max, nil]
try extractTest(testColumnName: "uhugeint", expected: expected) { $0.cast(to: UIntHuge.self) }
}
func test_extract_from_float() throws {
let expected = [-Float.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil]
try extractTest(testColumnName: "float", expected: expected) { $0.cast(to: Float.self) }
}
func test_extract_from_double() throws {
let expected = [-Double.greatestFiniteMagnitude, .greatestFiniteMagnitude, nil]
try extractTest(testColumnName: "double", expected: expected) { $0.cast(to: Double.self) }
}
func test_extract_from_varchar() throws {
let expected = ["🦆🦆🦆🦆🦆🦆", "goo\0se", nil]
try extractTest(testColumnName: "varchar", expected: expected) { $0.cast(to: String.self) }
}
func test_extract_from_uuid() throws {
let expected = [
UUID(uuidString: "00000000-0000-0000-0000-000000000000"),
UUID(uuidString: "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"),
nil
]
try extractTest(testColumnName: "uuid", expected: expected) { $0.cast(to: UUID.self) }
}
func test_extract_from_time() throws {
let expected = [
Time(components: .init(hour: 0, minute: 0, second: 0, microsecond: 0)),
Time(components: .init(hour: 24, minute: 0, second: 0, microsecond: 0)),
nil
]
try extractTest(testColumnName: "time", expected: expected) { $0.cast(to: Time.self) }
}
func test_extract_from_time_tz() throws {
let expected = [
TimeTz(time: Time(components: .init(hour: 0, minute: 0, second: 0, microsecond: 0)), offset: 57599),
TimeTz(time: Time(components: .init(hour: 24, minute: 0, second: 0, microsecond: 0)), offset: -57599),
nil
]
try extractTest(testColumnName: "time_tz", expected: expected) { $0.cast(to: TimeTz.self) }
}
func test_extract_from_date() throws {
let expected = [
Date(components: .init(year: -5_877_641, month: 06, day: 25)),
Date(components: .init(year: 5_881_580, month: 07, day: 10)),
nil
]
try extractTest(testColumnName: "date", expected: expected) { $0.cast(to: Date.self) }
}
func test_extract_from_timestamp() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 775_806)
let expected = [Timestamp(components: t1), Timestamp(components: t2), nil]
try extractTest(testColumnName: "timestamp", expected: expected) { $0.cast(to: Timestamp.self) }
}
func test_extract_from_timestamp_tz() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 775_806)
let expected = [Timestamp(components: t1), Timestamp(components: t2), nil]
try extractTest(
testColumnName: "timestamp_tz", expected: expected) { $0.cast(to: Timestamp.self) }
}
func test_extract_from_timestamp_s() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 0)
let expected = [Timestamp(components: t1), Timestamp(components: t2), nil]
try extractTest(
testColumnName: "timestamp_s", expected: expected) { $0.cast(to: Timestamp.self) }
}
func test_extract_from_timestamp_ms() throws {
let t1 = Timestamp.Components(
year: -290_308, month: 12, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 294_247, month: 01, day: 10, hour: 04, minute: 0, second: 54, microsecond: 775_000)
let expected = [Timestamp(components: t1), Timestamp(components: t2), nil]
try extractTest(
testColumnName: "timestamp_ms", expected: expected) { $0.cast(to: Timestamp.self) }
}
func test_extract_from_timestamp_ns() throws {
let t1 = Timestamp.Components(
year: 1677, month: 09, day: 22, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 2262, month: 04, day: 11, hour: 23, minute: 47, second: 16, microsecond: 854_775)
let expected = [Timestamp(components: t1), Timestamp(components: t2), nil]
try extractTest(
testColumnName: "timestamp_ns", expected: expected) { $0.cast(to: Timestamp.self) }
}
func test_extract_from_interval() throws {
let expected = [
Interval(
years: 0, months: 0, days: 0, hours: 0, minutes: 0, seconds: 0, microseconds: 0),
Interval(
years: 83, months: 3, days: 999, hours: 0, minutes: 16, seconds: 39, microseconds: 999_999),
nil
]
try extractTest(testColumnName: "interval", expected: expected) { $0.cast(to: Interval.self) }
}
func test_extract_from_blob() throws {
let expected = [
"thisisalongblob\0withnullbytes".data(using: .ascii)!,
"\0\0\0a".data(using: .ascii)!,
nil
]
try extractTest(testColumnName: "blob", expected: expected) { $0.cast(to: Data.self) }
}
func test_extract_from_decimal_4_1() throws {
let expected = [
Decimal(string: "-999.9"),
Decimal(string: " 999.9"),
nil
]
try extractTest(testColumnName: "dec_4_1", expected: expected) { $0.cast(to: Decimal.self) }
}
func test_extract_from_decimal_9_4() throws {
let expected = [
Decimal(string: "-99999.9999"),
Decimal(string: " 99999.9999"),
nil
]
try extractTest(testColumnName: "dec_9_4", expected: expected) { $0.cast(to: Decimal.self) }
}
func test_extract_from_decimal_18_6() throws {
let expected = [
Decimal(string: "-999999999999.999999"),
Decimal(string: "999999999999.999999"),
nil
]
try extractTest(testColumnName: "dec_18_6", expected: expected) { $0.cast(to: Decimal.self) }
}
func test_extract_from_decimal_38_10() throws {
let expected = [
Decimal(string: "-9999999999999999999999999999.9999999999"),
Decimal(string: "9999999999999999999999999999.9999999999"),
nil
]
try extractTest(testColumnName: "dec38_10", expected: expected) { $0.cast(to: Decimal.self) }
}
func test_extract_from_enum_small() throws {
enum SmallEnum: UInt8, RawRepresentable, Decodable {
case duckDuckEnum
case goose
}
let expected = [SmallEnum.duckDuckEnum, .goose, nil]
try extractTest(
testColumnName: "small_enum",
expected: expected,
params: "use_large_enum=true"
) { $0.cast(to: SmallEnum.self) }
}
func test_extract_from_enum_medium() throws {
enum MediumEnum: UInt16, RawRepresentable, Decodable {
case enum0
case enum299 = 299
}
let expected = [MediumEnum.enum0, .enum299, nil]
try extractTest(
testColumnName: "medium_enum",
expected: expected,
params: "use_large_enum=true"
) { $0.cast(to: MediumEnum.self) }
}
func test_extract_from_enum_large() throws {
enum LargeEnum: UInt32, RawRepresentable, Decodable {
case enum0
case enum69_999 = 69_999
}
let expected = [LargeEnum.enum0, .enum69_999, nil]
try extractTest(
testColumnName: "large_enum",
expected: expected,
params: "use_large_enum=true"
) { $0.cast(to: LargeEnum.self) }
}
func test_extract_from_int_array() throws {
let expected = [[], [Int32(42), 999, nil, nil, -42], nil]
try extractTest(testColumnName: "int_array", expected: expected) { $0.cast(to: [Int32?].self) }
}
func test_extract_from_int_array_casting_to_swift_int() throws {
let expected = [[], [Int(42), 999, nil, nil, -42], nil]
try extractTest(testColumnName: "int_array", expected: expected) { $0.cast(to: [Int?].self) }
}
func test_extract_from_double_array() throws {
// We need this contraption to work around .nan != .nan
enum DoubleBox: Equatable {
case normal(Double?)
case nan
init(_ source: Double?) {
switch source {
case let source? where source.isNaN:
self = .nan
case let source:
self = .normal(source)
}
}
}
let source = [[], [Double(42), .nan, .infinity, -.infinity, nil, -42], nil]
let expected = source.map { $0?.map(DoubleBox.init(_:)) }
let connection = try Database(store: .inMemory).connect()
let result = try connection.query("SELECT double_array FROM test_all_types();")
let column = result[0].cast(to: [Double?].self)
for (index, item) in expected.enumerated() {
XCTAssertEqual(column[DBInt(index)]?.map(DoubleBox.init(_:)), item)
}
}
func test_extract_from_date_array() throws {
let expected = [
[],
[
Date(components: .init(year: 1970, month: 01, day: 01)),
Date(days: 2147483647),
Date(days: -2147483647),
nil,
Date(components: .init(year: 2022, month: 05, day: 12)),
],
nil
]
try extractTest(
testColumnName: "date_array", expected: expected
) { $0.cast(to: [DuckDB.Date?].self) }
}
func test_extract_from_timestamptz_array() throws {
let t1 = Timestamp.Components(
year: 1970, month: 01, day: 01, hour: 0, minute: 0, second: 0, microsecond: 0)
let t2 = Timestamp.Components(
year: 2022, month: 05, day: 12, hour: 23, minute: 23, second: 45, microsecond: 0)
let expected = [
[],
[
Timestamp(components: t1),
Timestamp(microseconds: 9223372036854775807),
Timestamp(microseconds: -9223372036854775807),
nil,
Timestamp(components: t2),
],
nil
]
try extractTest(
testColumnName: "timestamptz_array", expected: expected
) { $0.cast(to: [Timestamp?].self) }
}
func test_extract_from_varchar_array() throws {
let expected = [[], ["🦆🦆🦆🦆🦆🦆", "goose", nil, ""], nil]
try extractTest(
testColumnName: "varchar_array", expected: expected
) { $0.cast(to: [String?].self) }
}
func test_extract_from_nested_int_array() throws {
let expected = [
[],
[[], [Int32(42), 999, nil, nil, -42], nil, [], [42, 999, nil, nil, -42]],
nil
]
try extractTest(
testColumnName: "nested_int_array", expected: expected
) { $0.cast(to: [[Int32?]?].self) }
}
func test_extract_from_struct() throws {
struct TestStruct: Decodable, Equatable {
var a: Int32? = nil
var b: String? = nil
}
let expected = [
TestStruct(),
TestStruct(a: 42, b: "🦆🦆🦆🦆🦆🦆"),
nil
]
try extractTest(testColumnName: "struct", expected: expected) { $0.cast(to: TestStruct.self) }
}
func test_extract_from_struct_of_arrays() throws {
struct TestStruct: Decodable, Equatable {
var a: [Int32?]? = nil
var b: [String?]? = nil
}
let expected = [
TestStruct(a: nil, b: nil),
TestStruct(a: [42, 999, nil, nil, -42], b: ["🦆🦆🦆🦆🦆🦆", "goose", nil, ""]),
nil
]
try extractTest(
testColumnName: "struct_of_arrays", expected: expected) { $0.cast(to: TestStruct.self) }
}
func test_extract_from_array_of_structs() throws {
struct TestStruct: Decodable, Equatable {
var a: Int32? = nil
var b: String? = nil
}
let expected = [
[],
[TestStruct(a: nil, b: nil), TestStruct(a: 42, b: "🦆🦆🦆🦆🦆🦆"), nil],
nil
]
try extractTest(
testColumnName: "array_of_structs", expected: expected) { $0.cast(to: [TestStruct?].self) }
}
func test_extract_from_map() throws {
let expected = [
Dictionary(),
Dictionary(uniqueKeysWithValues: [("key1", "🦆🦆🦆🦆🦆🦆"), ("key2", "goose")]),
nil
]
try extractTest(
testColumnName: "map", expected: expected) { $0.cast(to: [String: String].self) }
}
}
private extension TypeConversionTests {
func extractTest<T: Equatable>(
testColumnName: String,
expected: [T?],
params: String = "",
cast: (Column<Void>) -> Column<T>
) throws {
let connection = try Database(store: .inMemory).connect()
let result = try connection.query("SELECT \(testColumnName) FROM test_all_types(\(params));")
let column = cast(result[0])
for (index, item) in expected.enumerated() {
XCTAssertEqual(column[DBInt(index)], item)
}
}
}