From c4c048b181e3ef71a4c13c33c4cdd14e1fd750f7 Mon Sep 17 00:00:00 2001 From: hiimtmac Date: Thu, 25 Sep 2025 11:06:57 -0500 Subject: [PATCH 1/5] foundation --- Sources/Kuzu/Connection+Extensions.swift | 9 +++++++++ Sources/Kuzu/Connection.swift | 2 +- Sources/Kuzu/Database.swift | 3 +-- Sources/Kuzu/FlatTuple+Extensions.swift | 9 +++++++++ Sources/Kuzu/FlatTuple.swift | 3 +-- Sources/Kuzu/KuzuDecodable.swift | 13 +++++++++++++ Sources/Kuzu/KuzuEncodable.swift | 13 +++++++++++++ Sources/Kuzu/PreparedStatement.swift | 2 +- Sources/Kuzu/QueryResult.swift | 6 +++++- Sources/Kuzu/SystemConfig.swift | 6 +++++- Sources/Kuzu/Types+Extensions.swift | 13 +++++++++++++ Sources/Kuzu/Util+Extensions.swift | 13 +++++++++++++ Sources/Kuzu/Util.swift | 6 +++++- 13 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 Sources/Kuzu/Connection+Extensions.swift create mode 100644 Sources/Kuzu/FlatTuple+Extensions.swift create mode 100644 Sources/Kuzu/KuzuDecodable.swift create mode 100644 Sources/Kuzu/KuzuEncodable.swift create mode 100644 Sources/Kuzu/Types+Extensions.swift create mode 100644 Sources/Kuzu/Util+Extensions.swift diff --git a/Sources/Kuzu/Connection+Extensions.swift b/Sources/Kuzu/Connection+Extensions.swift new file mode 100644 index 00000000..e00eed52 --- /dev/null +++ b/Sources/Kuzu/Connection+Extensions.swift @@ -0,0 +1,9 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu + diff --git a/Sources/Kuzu/Connection.swift b/Sources/Kuzu/Connection.swift index d30fb948..7b09e3fb 100644 --- a/Sources/Kuzu/Connection.swift +++ b/Sources/Kuzu/Connection.swift @@ -5,7 +5,7 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) -@_implementationOnly import cxx_kuzu +import cxx_kuzu /// Represents a connection to a Kuzu database. public final class Connection: @unchecked Sendable { diff --git a/Sources/Kuzu/Database.swift b/Sources/Kuzu/Database.swift index 9e8b0aae..264f7edb 100644 --- a/Sources/Kuzu/Database.swift +++ b/Sources/Kuzu/Database.swift @@ -5,8 +5,7 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) -import Foundation -@_implementationOnly import cxx_kuzu +import cxx_kuzu /// A class representing a Kuzu database instance. public final class Database: @unchecked Sendable { diff --git a/Sources/Kuzu/FlatTuple+Extensions.swift b/Sources/Kuzu/FlatTuple+Extensions.swift new file mode 100644 index 00000000..e00eed52 --- /dev/null +++ b/Sources/Kuzu/FlatTuple+Extensions.swift @@ -0,0 +1,9 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu + diff --git a/Sources/Kuzu/FlatTuple.swift b/Sources/Kuzu/FlatTuple.swift index 1f352f55..00d50c28 100644 --- a/Sources/Kuzu/FlatTuple.swift +++ b/Sources/Kuzu/FlatTuple.swift @@ -5,8 +5,7 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) -import Foundation -@_implementationOnly import cxx_kuzu +import cxx_kuzu /// A class representing a row in the result set of a query. /// FlatTuple provides access to the values in a query result row and methods to convert them to different formats. diff --git a/Sources/Kuzu/KuzuDecodable.swift b/Sources/Kuzu/KuzuDecodable.swift new file mode 100644 index 00000000..dbef012b --- /dev/null +++ b/Sources/Kuzu/KuzuDecodable.swift @@ -0,0 +1,13 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif diff --git a/Sources/Kuzu/KuzuEncodable.swift b/Sources/Kuzu/KuzuEncodable.swift new file mode 100644 index 00000000..dbef012b --- /dev/null +++ b/Sources/Kuzu/KuzuEncodable.swift @@ -0,0 +1,13 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif diff --git a/Sources/Kuzu/PreparedStatement.swift b/Sources/Kuzu/PreparedStatement.swift index 90ebb41e..47c5599b 100644 --- a/Sources/Kuzu/PreparedStatement.swift +++ b/Sources/Kuzu/PreparedStatement.swift @@ -5,7 +5,7 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) -@_implementationOnly import cxx_kuzu +import cxx_kuzu /// A class representing a prepared statement in Kuzu. /// PreparedStatement can be used to execute a query with parameters. diff --git a/Sources/Kuzu/QueryResult.swift b/Sources/Kuzu/QueryResult.swift index e9982feb..2c1a81b5 100644 --- a/Sources/Kuzu/QueryResult.swift +++ b/Sources/Kuzu/QueryResult.swift @@ -5,8 +5,12 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation -@_implementationOnly import cxx_kuzu +#endif /// A class representing the result of a query, which can be used to iterate over the result set. /// QueryResult is returned by the `query` and `execute` methods of Connection. diff --git a/Sources/Kuzu/SystemConfig.swift b/Sources/Kuzu/SystemConfig.swift index bc65f21e..2f98d89a 100644 --- a/Sources/Kuzu/SystemConfig.swift +++ b/Sources/Kuzu/SystemConfig.swift @@ -5,8 +5,12 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation -@_implementationOnly import cxx_kuzu +#endif /// Represents the configuration of Kuzu database system. /// diff --git a/Sources/Kuzu/Types+Extensions.swift b/Sources/Kuzu/Types+Extensions.swift new file mode 100644 index 00000000..dbef012b --- /dev/null +++ b/Sources/Kuzu/Types+Extensions.swift @@ -0,0 +1,13 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif diff --git a/Sources/Kuzu/Util+Extensions.swift b/Sources/Kuzu/Util+Extensions.swift new file mode 100644 index 00000000..dbef012b --- /dev/null +++ b/Sources/Kuzu/Util+Extensions.swift @@ -0,0 +1,13 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else +import Foundation +#endif diff --git a/Sources/Kuzu/Util.swift b/Sources/Kuzu/Util.swift index 66c3d599..347172db 100644 --- a/Sources/Kuzu/Util.swift +++ b/Sources/Kuzu/Util.swift @@ -5,8 +5,12 @@ // Copyright © 2023 - 2025 Kùzu Inc. // This code is licensed under MIT license (see LICENSE for details) +import cxx_kuzu +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation -@_implementationOnly import cxx_kuzu +#endif /// Constants for time unit conversions private let MILLISECONDS_IN_A_SECOND: Double = 1_000 From 8efe92bcad15d9c45832808edbd82bf3a088edff Mon Sep 17 00:00:00 2001 From: hiimtmac Date: Fri, 26 Sep 2025 13:33:02 -0500 Subject: [PATCH 2/5] linux --- .devcontainer/devcontainer.json | 38 ++ Package.swift | 11 +- Sources/Kuzu/Connection+Extensions.swift | 45 ++ Sources/Kuzu/FlatTuple+Extensions.swift | 50 ++ Sources/Kuzu/KuzuDecodable.swift | 834 +++++++++++++++++++++++ Sources/Kuzu/KuzuEncodable.swift | 308 +++++++++ Sources/Kuzu/QueryResult.swift | 12 + Sources/Kuzu/Types+Extensions.swift | 145 ++++ Sources/Kuzu/Util+Extensions.swift | 13 - Sources/Kuzu/Util.swift | 18 +- Tests/kuzu-swiftTests/TypedTests.swift | 159 +++++ 11 files changed, 1609 insertions(+), 24 deletions(-) create mode 100644 .devcontainer/devcontainer.json delete mode 100644 Sources/Kuzu/Util+Extensions.swift create mode 100644 Tests/kuzu-swiftTests/TypedTests.swift diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b9a3c047 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,38 @@ +{ + "name": "Swift", + "image": "swift:6.1", + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installZsh": "false", + "username": "vscode", + // "userUid": "1000", + // "userGid": "1000", + "upgradePackages": "false" + }, + "ghcr.io/devcontainers/features/git:1": { + "version": "os-provided", + "ppa": "false" + } + }, + "runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"], + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "lldb.library": "/usr/lib/liblldb.so" + }, + // Add the IDs of extensions you want installed when the container is created. + "extensions": ["swiftlang.swift-vscode"] + } + }, + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "swift --version", + + // Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode" +} diff --git a/Package.swift b/Package.swift index 190311bc..7d951d23 100644 --- a/Package.swift +++ b/Package.swift @@ -1,8 +1,14 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription +let swiftSettings: [SwiftSetting] = [ + /// https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md + /// Require `any` for existential types. + .enableUpcomingFeature("ExistentialAny"), +] + let package = Package( name: "kuzu-swift", platforms: [ @@ -23,7 +29,8 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "Kuzu", - dependencies: ["cxx-kuzu"] + dependencies: ["cxx-kuzu"], + swiftSettings: swiftSettings ), .target( name: "cxx-kuzu", diff --git a/Sources/Kuzu/Connection+Extensions.swift b/Sources/Kuzu/Connection+Extensions.swift index e00eed52..e72e1868 100644 --- a/Sources/Kuzu/Connection+Extensions.swift +++ b/Sources/Kuzu/Connection+Extensions.swift @@ -7,3 +7,48 @@ import cxx_kuzu +@_spi(Typed) +extension Connection { + public func execute_( + _ preparedStatement: PreparedStatement, + _ parameters: [String: any KuzuEncodable] + ) throws -> QueryResult { + var cQueryResult = kuzu_query_result() + for (key, value) in parameters { + let kuzuValue = try value.kuzuValue() + let state = kuzu_prepared_statement_bind_value( + &preparedStatement.cPreparedStatement, + key, + kuzuValue.ptr + ) + if state != KuzuSuccess { + throw KuzuError.queryExecutionFailed( + "Failed to bind value with status \(state)" + ) + } + } + kuzu_connection_execute( + &cConnection, + &preparedStatement.cPreparedStatement, + &cQueryResult + ) + if !kuzu_query_result_is_success(&cQueryResult) { + let cErrorMesage: UnsafeMutablePointer? = + kuzu_query_result_get_error_message(&cQueryResult) + defer { + kuzu_query_result_destroy(&cQueryResult) + kuzu_destroy_string(cErrorMesage) + } + if cErrorMesage == nil { + throw KuzuError.queryExecutionFailed( + "Query execution failed with an unknown error." + ) + } else { + let errorMessage = String(cString: cErrorMesage!) + throw KuzuError.queryExecutionFailed(errorMessage) + } + } + let queryResult = QueryResult(self, cQueryResult) + return queryResult + } +} diff --git a/Sources/Kuzu/FlatTuple+Extensions.swift b/Sources/Kuzu/FlatTuple+Extensions.swift index e00eed52..39571c30 100644 --- a/Sources/Kuzu/FlatTuple+Extensions.swift +++ b/Sources/Kuzu/FlatTuple+Extensions.swift @@ -7,3 +7,53 @@ import cxx_kuzu +@_spi(Typed) +extension FlatTuple { + private func extractKuzu( + index: Int, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_flat_tuple_get_value(&cFlatTuple, UInt64(index), &cValue) + let value = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Get value failed with error code: \(state)" + ) + } + return value + } + + private func checkIndex(_ index: Int) throws { + let count = queryResult.getColumnCount() + guard index < count else { + throw KuzuError.getValueFailed( + "Index overflow on columns count of \(count)" + ) + } + } + + public subscript( + _ index: Int, + as type: T.Type = T.self + ) -> T where T: KuzuDecodable { + get throws { + try checkIndex(index) + var cValue = kuzu_value() + let value = try extractKuzu(index: index, into: &cValue) + return try T.kuzuDecode(from: value) + } + } + + // tuples cannot conform to protocols - helper to not have to specify `KuzuMap` + public subscript( + _ index: Int, + as type: [(T, U)].Type = [(T, U)].self + ) -> [(T, U)] where T: KuzuDecodable, U: KuzuDecodable { + get throws { + try checkIndex(index) + var cValue = kuzu_value() + let value = try extractKuzu(index: index, into: &cValue) + return try KuzuMap.kuzuDecode(from: value).tuples + } + } +} diff --git a/Sources/Kuzu/KuzuDecodable.swift b/Sources/Kuzu/KuzuDecodable.swift index dbef012b..4f97355c 100644 --- a/Sources/Kuzu/KuzuDecodable.swift +++ b/Sources/Kuzu/KuzuDecodable.swift @@ -11,3 +11,837 @@ import FoundationEssentials #else import Foundation #endif + +#if os(macOS) || os(iOS) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(Windows) +import ucrt +#else +#error("Unknown platform") +#endif + +@_spi(Typed) +public protocol KuzuDecodable { + static var kuzuDataTypes: [KuzuDataType] { get } + static func kuzuDecode(from container: consuming KuzuValue) throws -> Self +} + +@_spi(Typed) +extension KuzuValue { + fileprivate func nullCheck() throws { + if kuzu_value_is_null(ptr) { + throw KuzuError.getValueFailed( + "Value is null" + ) + } + } + + fileprivate func typeCheck(_ expecting: [KuzuDataType]) throws { + let typeId = LogicalType(from: self).id + guard expecting.contains(typeId) else { + throw KuzuError.valueConversionFailed( + "Received \(typeId) type when expecting \(expecting)" + ) + } + } +} + + +@_spi(Typed) +extension Bool: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.bool] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Bool = Bool() + let state = kuzu_value_get_bool(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get bool value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Int64: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.int64, .serial] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Int64 = Int64() + let state = kuzu_value_get_int64(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get int64 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension UInt64: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.uint64] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: UInt64 = UInt64() + let state = kuzu_value_get_uint64(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get uint64 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Int32: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.int32] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Int32 = Int32() + let state = kuzu_value_get_int32(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get int32 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension UInt32: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.uint32] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: UInt32 = UInt32() + let state = kuzu_value_get_uint32(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get uint32 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Int16: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.int16] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Int16 = Int16() + let state = kuzu_value_get_int16(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get int16 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension UInt16: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.uint16] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: UInt16 = UInt16() + let state = kuzu_value_get_uint16(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get uint16 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Int8: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.int8] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Int8 = Int8() + let state = kuzu_value_get_int8(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get int8 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension UInt8: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.uint8] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: UInt8 = UInt8() + let state = kuzu_value_get_uint8(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get uint8 value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Float: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.float] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Float = Float() + let state = kuzu_value_get_float(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get Float value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension Double: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.double] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var value: Double = Double() + let state = kuzu_value_get_double(container.ptr, &value) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get Double value with status \(state)" + ) + } + return value + } +} + +@_spi(Typed) +extension KuzuInterval: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.interval] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var cIntervalValue = kuzu_interval_t() + let state = kuzu_value_get_interval(container.ptr, &cIntervalValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get interval value with status \(state)" + ) + } + return KuzuInterval( + months: cIntervalValue.months, + days: cIntervalValue.days, + micros: cIntervalValue.micros + ) + } +} + +@_spi(Typed) +extension String: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.string] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var strValue: UnsafeMutablePointer? + let state = kuzu_value_get_string(container.ptr, &strValue) + defer { kuzu_destroy_string(strValue) } + guard state == KuzuSuccess, let strValue else { + throw KuzuError.getValueFailed( + "Failed to get string value with status \(state)" + ) + } + return String(cString: strValue) + } +} + +@_spi(Typed) +extension UUID: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.uuid] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var valueString: UnsafeMutablePointer? + let state = kuzu_value_get_uuid(container.ptr, &valueString) + defer { kuzu_destroy_string(valueString) } + guard state == KuzuSuccess, let valueString, let uuid = UUID(uuidString: String(cString: valueString)) else { + throw KuzuError.getValueFailed( + "Failed to get uuid value with status \(state)" + ) + } + return uuid + } +} + +@_spi(Typed) +extension Date: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.date, .timestamp, .timestampMs, .timestampNs, .timestampTz, .timestampSec] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + + let typeId = LogicalType(from: container).id + switch typeId { + case .date: return try kuzuDate(from: container) + case .timestamp: return try kuzuTimestamp(from: container) + case .timestampMs: return try kuzuTimestampMs(from: container) + case .timestampNs: return try kuzuTimestampNs(from: container) + case .timestampTz: return try kuzuTimestampTz(from: container) + case .timestampSec: return try kuzuTimestampSec(from: container) + default: + throw KuzuError.valueConversionFailed( + "Received \(typeId) type when expecting \(Self.kuzuDataTypes)" + ) + } + } + + static func kuzuTimestamp(from container: consuming KuzuValue) throws -> Self { + var cTimestampValue = kuzu_timestamp_t() + let state = kuzu_value_get_timestamp(container.ptr, &cTimestampValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get timestamp value with status \(state)" + ) + } + let microseconds = cTimestampValue.value + let seconds: Double = Double(microseconds) / MICROSECONDS_IN_A_SECOND + return Date(timeIntervalSince1970: seconds) + } + + static func kuzuTimestampNs(from container: consuming KuzuValue) throws -> Self { + var cTimestampValue = kuzu_timestamp_ns_t() + let state = kuzu_value_get_timestamp_ns(container.ptr, &cTimestampValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get timestamp value with status \(state)" + ) + } + let nanoseconds = cTimestampValue.value + let seconds: Double = Double(nanoseconds) / NANOSECONDS_IN_A_SECOND + return Date(timeIntervalSince1970: seconds) + } + + static func kuzuTimestampMs(from container: consuming KuzuValue) throws -> Self { + var cTimestampValue = kuzu_timestamp_ms_t() + let state = kuzu_value_get_timestamp_ms(container.ptr, &cTimestampValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get timestamp value with status \(state)" + ) + } + let milliseconds = cTimestampValue.value + let seconds: Double = Double(milliseconds) / MILLISECONDS_IN_A_SECOND + return Date(timeIntervalSince1970: seconds) + } + + static func kuzuTimestampSec(from container: consuming KuzuValue) throws -> Self { + var cTimestampValue = kuzu_timestamp_sec_t() + let state = kuzu_value_get_timestamp_sec(container.ptr, &cTimestampValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get timestamp value with status \(state)" + ) + } + let seconds = cTimestampValue.value + return Date(timeIntervalSince1970: Double(seconds)) + } + + static func kuzuTimestampTz(from container: consuming KuzuValue) throws -> Self { + var cTimestampValue = kuzu_timestamp_tz_t() + let state = kuzu_value_get_timestamp_tz(container.ptr, &cTimestampValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get timestamp value with status \(state)" + ) + } + let microseconds = cTimestampValue.value + let seconds: Double = Double(microseconds) / MICROSECONDS_IN_A_SECOND + return Date(timeIntervalSince1970: seconds) + } + + static func kuzuDate(from container: consuming KuzuValue) throws -> Self { + var cDateValue = kuzu_date_t() + let state = kuzu_value_get_date(container.ptr, &cDateValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get date value with status \(state)" + ) + } + let days = cDateValue.days + let seconds: Double = Double(days) * SECONDS_IN_A_DAY + return Date(timeIntervalSince1970: seconds) + } +} + +@_spi(Typed) +extension Decimal: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.decimal, .int128] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + + let typeId = LogicalType(from: container).id + switch typeId { + case .decimal: return try kuzuDecimal(from: container) + case .int128: return try kuzuInt128(from: container) + default: + throw KuzuError.valueConversionFailed( + "Received \(typeId) type when expecting \(Self.kuzuDataTypes)" + ) + } + } + + static func kuzuDecimal(from container: consuming KuzuValue) throws -> Self { + var outString: UnsafeMutablePointer? + let state = kuzu_value_get_decimal_as_string(container.ptr, &outString) + defer { kuzu_destroy_string(outString) } + guard state == KuzuSuccess, let outString else { + throw KuzuError.getValueFailed( + "Failed to get string value of decimal type with status: \(state)" + ) + } + let decimalString = String(cString: outString) + guard let decimal = Decimal(string: decimalString) else { + throw KuzuError.valueConversionFailed( + "Failed to convert decimal value from string: \(decimalString)" + ) + } + return decimal + } + + static func kuzuInt128(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + var int128Value = kuzu_int128_t() + let getValueState = kuzu_value_get_int128(container.ptr, &int128Value) + guard getValueState == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get int128 value with status \(getValueState)" + ) + } + var valueString: UnsafeMutablePointer? + let valueConversionState = kuzu_int128_t_to_string( + int128Value, + &valueString + ) + defer { kuzu_destroy_string(valueString) } + guard valueConversionState == KuzuSuccess, let valueString else { + throw KuzuError.getValueFailed( + "Failed to convert int128 to string with status \(valueConversionState)" + ) + } + let decimalString = String(cString: valueString) + guard let decimal = Decimal(string: decimalString) else { + throw KuzuError.valueConversionFailed( + "Failed to convert decimal value from string: \(decimalString)" + ) + } + return decimal + } +} + +@_spi(Typed) +extension KuzuInternalId: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.internalId] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var cInternalIdValue = kuzu_internal_id_t() + let state = kuzu_value_get_internal_id(container.ptr, &cInternalIdValue) + guard state == KuzuSuccess else { + throw KuzuError.getValueFailed( + "Failed to get internal id value with status \(state)" + ) + } + return KuzuInternalId( + tableId: cInternalIdValue.table_id, + offset: cInternalIdValue.offset + ) + } +} + +@_spi(Typed) +extension Data: KuzuDecodable { + public static let kuzuDataTypes: [KuzuDataType] = [.blob] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var cBlobValue: UnsafeMutablePointer? + let state = kuzu_value_get_blob(container.ptr, &cBlobValue) + defer { kuzu_destroy_blob(cBlobValue) } + guard state == KuzuSuccess, let cBlobValue else { + throw KuzuError.getValueFailed( + "Failed to get blob value with status \(state)" + ) + } + let blobSize = strlen(cBlobValue) + let blobData = Data(bytes: cBlobValue, count: blobSize) + return blobData + } +} + +@_spi(Typed) +extension Optional: KuzuDecodable where Wrapped: KuzuDecodable { + public static var kuzuDataTypes: [KuzuDataType] { Wrapped.kuzuDataTypes } + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + if kuzu_value_is_null(container.ptr) { + return nil + } + + return try Wrapped.kuzuDecode(from: container) + } +} + +@_spi(Typed) +extension Array: KuzuDecodable where Element: KuzuDecodable { + public static var kuzuDataTypes: [KuzuDataType] { [.list, .array] } + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + let logicalType = LogicalType(from: container) + let numElements: UInt64 + + switch logicalType.id { + case .array: + numElements = try logicalType.kuzuArrayElementCount() + case .list: + numElements = try container.kuzuListElementCount() + default: + throw KuzuError.valueConversionFailed( + "Failed to get number of elements - unknown list/array type \(logicalType.id)" + ) + } + + var result: [Element] = [] + for i in UInt64(0).. Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + let propertySize = try container.kuzuStructElementCount() + + var dict: [String: Value] = [:] + for i in UInt64(0).. Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + let mapSize = try container.kuzuMapElementCount() + + var result: [(Key, Value)] = [] + for i in UInt64(0).. Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var idValue = kuzu_value() + let idState = kuzu_node_val_get_id_val(container.ptr, &idValue) + let kuzuId = KuzuValue(ptr: &idValue) + guard idState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get node ID with status: \(idState)" + ) + } + let id = try KuzuInternalId.kuzuDecode(from: kuzuId) + + var labelValue = kuzu_value() + let labelState = kuzu_node_val_get_label_val(container.ptr, &labelValue) + let kuzuLabel = KuzuValue(ptr: &labelValue) + guard labelState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get node label with status: \(labelState)" + ) + } + let label = try String.kuzuDecode(from: kuzuLabel) + + let propertySize = try container.kuzuNodeElementCount() + var properties: [String: any KuzuDecodable] = [:] + + for i in UInt64(0).. Self { + try container.nullCheck() + let type = LogicalType(from: container) + let decoded = try type.id.decode(from: container) + return .init(value: decoded) + } +} + +// MARK: Helpers + +extension KuzuValue { + fileprivate func getListValue( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_value_get_list_element(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get list element with status: \(state)" + ) + } + return kuzu + } + + fileprivate func getStructValue( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_value_get_struct_field_value(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get struct field with status: \(state)" + ) + } + return kuzu + } + + fileprivate func getStructKey(index: UInt64) throws -> String { + var currentKey: UnsafeMutablePointer? + let keyState = kuzu_value_get_struct_field_name(ptr, index, ¤tKey) + defer { kuzu_destroy_string(currentKey) } + guard keyState == KuzuSuccess, let currentKey else { + throw KuzuError.valueConversionFailed( + "Failed to get struct field name with status: \(keyState)" + ) + } + return String(cString: currentKey) + } + + fileprivate func getMapValue( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_value_get_map_value(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get map value with status: \(state)" + ) + } + return kuzu + } + + fileprivate func getMapKey( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_value_get_map_key(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get map key with status: \(state)" + ) + } + return kuzu + } + + fileprivate func getNodeValue( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_node_val_get_property_value_at(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get node property value with status: \(state)" + ) + } + return kuzu + } + + fileprivate func getNodeKey(index: UInt64) throws -> String { + var currentKey: UnsafeMutablePointer? + let keyState = kuzu_node_val_get_property_name_at(ptr, index, ¤tKey) + defer { kuzu_destroy_string(currentKey) } + guard keyState == KuzuSuccess, let currentKey else { + throw KuzuError.valueConversionFailed( + "Failed to get node propery name with status: \(keyState)" + ) + } + return String(cString: currentKey) + } + + fileprivate func kuzuListElementCount() throws -> UInt64 { + var numElements: UInt64 = 0 + + let state = kuzu_value_get_list_size(ptr, &numElements) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in list with status: \(state)" + ) + } + return numElements + } + + fileprivate func kuzuStructElementCount() throws -> UInt64 { + var propertySize: UInt64 = 0 + + let state = kuzu_value_get_struct_num_fields(ptr, &propertySize) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in struct with status: \(state)" + ) + } + + return propertySize + } + + fileprivate func kuzuMapElementCount() throws -> UInt64 { + var mapSize: UInt64 = 0 + + let state = kuzu_value_get_map_size(ptr, &mapSize) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in map with status: \(state)" + ) + } + + return mapSize + } + + fileprivate func kuzuNodeElementCount() throws -> UInt64 { + var propertySize: UInt64 = 0 + + let state = kuzu_node_val_get_property_size(ptr, &propertySize) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in node with status: \(state)" + ) + } + + return propertySize + } +} + +extension LogicalType { + fileprivate func kuzuArrayElementCount() throws -> UInt64 { + var numElements: UInt64 = 0 + + let state = kuzu_data_type_get_num_elements_in_array( + ptr, + &numElements + ) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in array with status: \(state)" + ) + } + + return numElements + } +} + +#warning("Implement me") +// switch logicalTypeId { +// case KUZU_UNION: +// return try kuzuUnionValueToSwiftValue(&cValue) +// case KUZU_NODE: +// return try kuzuNodeValueToSwiftNode(&cValue) +// case KUZU_REL: +// return try kuzuRelValueToSwiftRelationship(&cValue) +// case KUZU_RECURSIVE_REL: +// return try kuzuRecursiveRelValueToSwiftRecursiveRelationship(&cValue) +// ) +// } diff --git a/Sources/Kuzu/KuzuEncodable.swift b/Sources/Kuzu/KuzuEncodable.swift index dbef012b..81141cc2 100644 --- a/Sources/Kuzu/KuzuEncodable.swift +++ b/Sources/Kuzu/KuzuEncodable.swift @@ -6,8 +6,316 @@ // This code is licensed under MIT license (see LICENSE for details) import cxx_kuzu + #if canImport(FoundationEssentials) import FoundationEssentials #else import Foundation #endif + +#if os(macOS) || os(iOS) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(Windows) +import ucrt +#else +#error("Unknown platform") +#endif + +@_spi(Typed) +public protocol KuzuEncodable { + func kuzuValue() throws -> KuzuValue +} + +@_spi(Typed) +extension Optional: KuzuEncodable where Wrapped: KuzuEncodable { + public func kuzuValue() throws -> KuzuValue { + if let self { + try self.kuzuValue() + } else { + KuzuValue.null() + } + } +} + +@_spi(Typed) +extension Array: KuzuEncodable where Element: KuzuEncodable { + public func kuzuValue() throws -> KuzuValue { + try ListContainer(items: self).kuzuValue() + } +} + +@_spi(Typed) +extension Dictionary: KuzuEncodable where Key == String, Value: KuzuEncodable { + public func kuzuValue() throws -> KuzuValue { + try StructContainer(items: self).kuzuValue() + } +} + +@_spi(Typed) +extension KuzuMap: KuzuEncodable where Key: KuzuEncodable, Value: KuzuEncodable { + public func kuzuValue() throws -> KuzuValue { + try MapContainer(items: tuples).kuzuValue() + } +} + +@_spi(Typed) +extension Bool: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_bool(self)) + } +} + +@_spi(Typed) +extension Int8: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_int8(self)) + } +} + +@_spi(Typed) +extension UInt8: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_uint8(self)) + } +} + +@_spi(Typed) +extension Int16: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_int16(self)) + } +} + +@_spi(Typed) +extension UInt16: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_uint16(self)) + } +} + +@_spi(Typed) +extension Int32: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_int32(self)) + } +} + +@_spi(Typed) +extension UInt32: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_uint32(self)) + } +} + +@_spi(Typed) +extension Int64: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_int64(self)) + } +} + +@_spi(Typed) +extension UInt64: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_uint64(self)) + } +} + +@_spi(Typed) +extension Float: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_float(self)) + } +} + +@_spi(Typed) +extension Double: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_double(self)) + } +} + +@_spi(Typed) +extension String: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_string(self)) + } +} + +@_spi(Typed) +extension Date: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + let timeInterval = self.timeIntervalSince1970 + let microseconds = timeInterval * MICROSECONDS_IN_A_SECOND + let cValue = kuzu_timestamp_t(value: Int64(microseconds)) + return .init(ptr: kuzu_value_create_timestamp(cValue)) + } +} + +@_spi(Typed) +extension UUID: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_string(uuidString)) + } +} + +@_spi(Typed) +extension KuzuInterval: KuzuEncodable { + public func kuzuValue() -> KuzuValue { + .init(ptr: kuzu_value_create_interval(kuzu_interval_t(months: months, days: days, micros: micros))) + } +} + +private final class Box { + var value: T + init(_ value: consuming T) { + self.value = value + } +} + +private final class ListContainer: KuzuEncodable { + let count: Int + let items: [Box] + + init(items: [some KuzuEncodable]) throws { + self.count = items.count + self.items = try items.map { try Box($0.kuzuValue()) } + } + + var pointers: [UnsafeMutablePointer?] { + items.map { $0.value.ptr } + } + + func kuzuValue() throws -> KuzuValue { + guard count > 0 else { + throw KuzuError.valueConversionFailed( + "Cannot convert empty array to Kuzu list" + ) + } + + var outValue: UnsafeMutablePointer? + let state = pointers.withUnsafeBufferPointer { buffer in + kuzu_value_create_list( + UInt64(count), + UnsafeMutablePointer(mutating: buffer.baseAddress), + &outValue + ) + } + + guard state == KuzuSuccess, let outValue else { + throw KuzuError.valueConversionFailed( + "Failed to create list value with status: \(state)" + ) + } + + return KuzuValue(ptr: outValue) + } +} + +private final class StructContainer: KuzuEncodable { + let count: Int + let keys: [Box] + let values: [Box] + + init(items: [String: some KuzuEncodable]) throws { + self.count = items.count + self.keys = items.map { Box(CString($0.key)) } + self.values = try items.map { try Box($0.value.kuzuValue()) } + } + + var valuePointers: [UnsafeMutablePointer?] { + values.map { $0.value.ptr } + } + + var charPointers: [UnsafePointer?] { + keys.map { UnsafePointer($0.value.ptr) } + } + + struct CString: ~Copyable { + let ptr: UnsafeMutablePointer? + + init(_ string: String) { + self.ptr = strdup(string) + } + + deinit { + free(ptr) + } + } + + func kuzuValue() throws -> KuzuValue { + guard count > 0 else { + throw KuzuError.valueConversionFailed("Cannot convert empty dictionary to Kuzu struct") + } + + var outValue: UnsafeMutablePointer? + let state = charPointers.withUnsafeBufferPointer { namesBuffer in + valuePointers.withUnsafeBufferPointer { valuesBuffer in + kuzu_value_create_struct( + UInt64(count), + UnsafeMutablePointer(mutating: namesBuffer.baseAddress), + UnsafeMutablePointer(mutating: valuesBuffer.baseAddress), + &outValue + ) + } + } + + guard state == KuzuSuccess, let outValue else { + throw KuzuError.valueConversionFailed( + "Failed to create struct value with status: \(state)" + ) + } + + return KuzuValue(ptr: outValue) + } +} + +private final class MapContainer: KuzuEncodable { + let count: Int + let keys: [Box] + let values: [Box] + + init(items: [(some KuzuEncodable, some KuzuEncodable)]) throws { + self.count = items.count + self.keys = try items.map { try Box($0.0.kuzuValue()) } + self.values = try items.map { try Box($0.1.kuzuValue()) } + } + + var valuePointers: [UnsafeMutablePointer?] { + values.map { $0.value.ptr } + } + + var keyPointers: [UnsafeMutablePointer?] { + keys.map { $0.value.ptr } + } + + func kuzuValue() throws -> KuzuValue { + guard count > 0 else { + throw KuzuError.valueConversionFailed("Cannot convert empty dictionary to Kuzu struct") + } + + var outValue: UnsafeMutablePointer? + let state = keyPointers.withUnsafeBufferPointer { keysBuffer in + valuePointers.withUnsafeBufferPointer { valuesBuffer in + kuzu_value_create_map( + UInt64(count), + UnsafeMutablePointer(mutating: keysBuffer.baseAddress), + UnsafeMutablePointer(mutating: valuesBuffer.baseAddress), + &outValue + ) + } + } + + guard state == KuzuSuccess, let outValue else { + throw KuzuError.valueConversionFailed( + "Failed to create MAP value with status: \(state). Please make sure all the keys are of the same type and all the values are of the same type." + ) + } + + return KuzuValue(ptr: outValue) + } +} diff --git a/Sources/Kuzu/QueryResult.swift b/Sources/Kuzu/QueryResult.swift index 2c1a81b5..4261ad16 100644 --- a/Sources/Kuzu/QueryResult.swift +++ b/Sources/Kuzu/QueryResult.swift @@ -12,6 +12,18 @@ import FoundationEssentials import Foundation #endif +#if os(macOS) || os(iOS) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(Windows) +import ucrt +#else +#error("Unknown platform") +#endif + /// A class representing the result of a query, which can be used to iterate over the result set. /// QueryResult is returned by the `query` and `execute` methods of Connection. /// It conforms to `CustomStringConvertible` and `Sequence` protocols for easy string representation and iteration. diff --git a/Sources/Kuzu/Types+Extensions.swift b/Sources/Kuzu/Types+Extensions.swift index dbef012b..abba26c1 100644 --- a/Sources/Kuzu/Types+Extensions.swift +++ b/Sources/Kuzu/Types+Extensions.swift @@ -11,3 +11,148 @@ import FoundationEssentials #else import Foundation #endif + +@_spi(Typed) +public struct KuzuValue: ~Copyable { + let ptr: UnsafeMutablePointer + + init(ptr: UnsafeMutablePointer) { + self.ptr = ptr + } + + static func null() -> KuzuValue { + KuzuValue(ptr: kuzu_value_create_null()) + } + + deinit { + kuzu_value_destroy(ptr) + } +} + +struct LogicalType: ~Copyable { + let ptr: UnsafeMutablePointer + + init(from value: borrowing KuzuValue) { + ptr = .allocate(capacity: 1) + ptr.initialize(to: kuzu_logical_type()) + kuzu_value_get_data_type(value.ptr, ptr) + } + + var id: KuzuDataType { + .init(id: kuzu_data_type_get_id(ptr)) + } + + deinit { + kuzu_data_type_destroy(ptr) + ptr.deinitialize(count: 1) + ptr.deallocate() + } +} + +@_spi(Typed) +public struct KuzuInterval { + public let months: Int32 + public let days: Int32 + public let micros: Int64 +} + +extension KuzuInterval { + public init(_ timeInterval: TimeInterval) { + self.init( + months: 0, + days: 0, + micros: Int64(timeInterval * MICROSECONDS_IN_A_SECOND) + ) + } +} + +@_spi(Typed) +public struct KuzuNode_ { + /// The internal ID of the node. + public let id: KuzuInternalId + /// The label of the node. + public let label: String + /// The properties of the node, where keys are property names and values are property values. + public let properties: [String: any KuzuDecodable] +} + +// Tuples cannot conform to protocols so we need this wrapper +@_spi(Typed) +public struct KuzuMap { + public let tuples: [(Key, Value)] + + public init(_ tuples: [(Key, Value)]) { + self.tuples = tuples + } + + subscript(idx: Int) -> (Key, Value) { + tuples[idx] + } +} + +@_spi(Typed) +public struct KuzuDataType: Equatable, Sendable { + let id: kuzu_data_type_id + + public static let bool = KuzuDataType(id: KUZU_BOOL) + public static let serial = KuzuDataType(id: KUZU_SERIAL) + public static let int128 = KuzuDataType(id: KUZU_INT128) + public static let int64 = KuzuDataType(id: KUZU_INT64) + public static let uint64 = KuzuDataType(id: KUZU_UINT64) + public static let int32 = KuzuDataType(id: KUZU_INT32) + public static let uint32 = KuzuDataType(id: KUZU_UINT32) + public static let int16 = KuzuDataType(id: KUZU_INT16) + public static let uint16 = KuzuDataType(id: KUZU_UINT16) + public static let int8 = KuzuDataType(id: KUZU_INT8) + public static let uint8 = KuzuDataType(id: KUZU_UINT8) + public static let float = KuzuDataType(id: KUZU_FLOAT) + public static let double = KuzuDataType(id: KUZU_DOUBLE) + public static let interval = KuzuDataType(id: KUZU_INTERVAL) + public static let string = KuzuDataType(id: KUZU_STRING) + public static let uuid = KuzuDataType(id: KUZU_UUID) + public static let date = KuzuDataType(id: KUZU_DATE) + public static let timestamp = KuzuDataType(id: KUZU_TIMESTAMP) + public static let timestampSec = KuzuDataType(id: KUZU_TIMESTAMP_SEC) + public static let timestampNs = KuzuDataType(id: KUZU_TIMESTAMP_NS) + public static let timestampMs = KuzuDataType(id: KUZU_TIMESTAMP_MS) + public static let timestampTz = KuzuDataType(id: KUZU_TIMESTAMP_TZ) + public static let decimal = KuzuDataType(id: KUZU_DECIMAL) + public static let internalId = KuzuDataType(id: KUZU_INTERNAL_ID) + public static let blob = KuzuDataType(id: KUZU_BLOB) + public static let list = KuzuDataType(id: KUZU_LIST) + public static let array = KuzuDataType(id: KUZU_ARRAY) + public static let map = KuzuDataType(id: KUZU_MAP) + public static let `struct` = KuzuDataType(id: KUZU_STRUCT) + public static let node = KuzuDataType(id: KUZU_NODE) + public static let any = KuzuDataType(id: KUZU_ANY) + + func decode(from container: consuming KuzuValue) throws -> any KuzuDecodable { + switch self { + case .bool: try Bool.kuzuDecode(from: container) + case .serial, .int64: try Int64.kuzuDecode(from: container) + case .int64: try Int64.kuzuDecode(from: container) + case .uint64: try UInt64.kuzuDecode(from: container) + case .int32: try Int32.kuzuDecode(from: container) + case .uint32: try UInt32.kuzuDecode(from: container) + case .int16: try Int16.kuzuDecode(from: container) + case .uint16: try UInt16.kuzuDecode(from: container) + case .int8: try Int8.kuzuDecode(from: container) + case .uint8: try UInt8.kuzuDecode(from: container) + case .float: try Float.kuzuDecode(from: container) + case .double: try Double.kuzuDecode(from: container) + case .interval: try KuzuInterval.kuzuDecode(from: container) + case .string: try String.kuzuDecode(from: container) + case .uuid: try UUID.kuzuDecode(from: container) + case .date, .timestamp, .timestampSec, .timestampNs, .timestampMs, .timestampTz: try Date.kuzuDecode(from: container) + case .decimal, .int128: try Decimal.kuzuDecode(from: container) + case .internalId: try KuzuInternalId.kuzuDecode(from: container) + case .blob: try Data.kuzuDecode(from: container) + case .list, .array: try Array.kuzuDecode(from: container) + case .map: try Dictionary.kuzuDecode(from: container) + case .struct:try Dictionary.kuzuDecode(from: container) + case .node: try KuzuNode_.kuzuDecode(from: container) + default: + throw KuzuError.valueConversionFailed("Unsupported type: \(self)") + } + } +} diff --git a/Sources/Kuzu/Util+Extensions.swift b/Sources/Kuzu/Util+Extensions.swift deleted file mode 100644 index dbef012b..00000000 --- a/Sources/Kuzu/Util+Extensions.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// kuzu-swift -// https://github.com/kuzudb/kuzu-swift -// -// Copyright © 2023 - 2025 Kùzu Inc. -// This code is licensed under MIT license (see LICENSE for details) - -import cxx_kuzu -#if canImport(FoundationEssentials) -import FoundationEssentials -#else -import Foundation -#endif diff --git a/Sources/Kuzu/Util.swift b/Sources/Kuzu/Util.swift index 347172db..754bea22 100644 --- a/Sources/Kuzu/Util.swift +++ b/Sources/Kuzu/Util.swift @@ -6,25 +6,25 @@ // This code is licensed under MIT license (see LICENSE for details) import cxx_kuzu -#if canImport(FoundationEssentials) -import FoundationEssentials -#else +// #if canImport(FoundationEssentials) +// import FoundationEssentials +// #else import Foundation -#endif +// #endif /// Constants for time unit conversions -private let MILLISECONDS_IN_A_SECOND: Double = 1_000 -private let MICROSECONDS_IN_A_MILLISECOND: Double = 1_000 -private let MICROSECONDS_IN_A_SECOND: Double = +let MILLISECONDS_IN_A_SECOND: Double = 1_000 +let MICROSECONDS_IN_A_MILLISECOND: Double = 1_000 +let MICROSECONDS_IN_A_SECOND: Double = MILLISECONDS_IN_A_SECOND * MICROSECONDS_IN_A_MILLISECOND private let NANOSECONDS_IN_A_MICROSECOND: Double = 1_000 -private let NANOSECONDS_IN_A_SECOND: Double = +let NANOSECONDS_IN_A_SECOND: Double = MICROSECONDS_IN_A_SECOND * NANOSECONDS_IN_A_MICROSECOND private let SECONDS_IN_A_MINUTE: Double = 60 private let MINUTES_IN_AN_HOUR: Double = 60 private let HOURS_IN_A_DAY: Double = 24 private let DAYS_IN_A_MONTH: Double = 30 -private let SECONDS_IN_A_DAY = +let SECONDS_IN_A_DAY = HOURS_IN_A_DAY * MINUTES_IN_AN_HOUR * SECONDS_IN_A_MINUTE private let SECONDS_IN_A_MONTH = DAYS_IN_A_MONTH * SECONDS_IN_A_DAY diff --git a/Tests/kuzu-swiftTests/TypedTests.swift b/Tests/kuzu-swiftTests/TypedTests.swift new file mode 100644 index 00000000..aa7a45bd --- /dev/null +++ b/Tests/kuzu-swiftTests/TypedTests.swift @@ -0,0 +1,159 @@ +// +// kuzu-swift +// https://github.com/kuzudb/kuzu-swift +// +// Copyright © 2023 - 2025 Kùzu Inc. +// This code is licensed under MIT license (see LICENSE for details) + +import Foundation +import Testing +@testable @_spi(Typed) import Kuzu + +@Suite +struct TypedTests { + let conn: Connection + + init() throws { + let db = try Database() + self.conn = try Connection(db) + } + + @Test + func testExecutePrimatives() throws { + let stmt = try conn.prepare(""" + RETURN + $bool, + $int8, + $uint8, + $int16, + $uint16, + $int32, + $uint32, + $int64, + $uint64, + $float, + $double, + $string, + TIMESTAMP($date), + INTERVAL($interval), + UUID($uuid) + ; + """) + let result = try conn.execute_(stmt, [ + "bool": true, + "int8": Int8(8), + "uint8": UInt8(8), + "int16": Int16(16), + "uint16": UInt16(16), + "int32": Int32(32), + "uint32": UInt32(32), + "int64": Int64(64), + "uint64": UInt64(64), + "float": 3.14, + "double": 3.14159, + "string": "hello", + "date": Date(timeIntervalSince1970: 3600), + "interval": KuzuInterval(1000.0), + "uuid": UUID(uuidString: "83313081-D4D3-4175-B118-BCCE83E708D1") + ]) + + let tuple = try #require(try result.getNext()) + #expect(try tuple[0]) + #expect(try tuple[1, as: Int8.self] == 8) + #expect(try tuple[2, as: UInt8.self] == 8) + #expect(try tuple[3, as: Int16.self] == 16) + #expect(try tuple[4, as: UInt16.self] == 16) + #expect(try tuple[5, as: Int32.self] == 32) + #expect(try tuple[6, as: UInt32.self] == 32) + #expect(try tuple[7, as: Int64.self] == 64) + #expect(try tuple[8, as: UInt64.self] == 64) + #expect(try tuple[9, as: Double.self] == 3.14) + #expect(try tuple[10, as: Double.self] == 3.14159) + #expect(try tuple[11, as: String.self] == "hello") + #expect(try tuple[12, as: Date.self].timeIntervalSince1970 == 3600) + #expect(try tuple[13, as: KuzuInterval.self].micros == 1000000000) + #expect(try tuple[14, as: UUID.self] == UUID(uuidString: "83313081-D4D3-4175-B118-BCCE83E708D1")) + } + + @Test + func testExecuteNil() throws { + let stmt = try conn.prepare(""" + RETURN + $nilInt32, + $notNilInt32, + $nilString, + $notNilString + ; + """) + let result = try conn.execute_(stmt, [ + "nilInt32": nil as Int32?, + "notNilInt32": 1 as Int32?, + "nilString": nil as String?, + "notNilString": "hello" as String? + ]) + + let tuple = try #require(try result.getNext()) + #expect(try tuple[0, as: Int32?.self] == nil) + #expect(try tuple[1, as: Int32?.self] == 1) + #expect(try tuple[2, as: String?.self] == nil) + #expect(try tuple[3, as: String?.self] == "hello") + } + + @Test + func testExecuteList() throws { + let stmt = try conn.prepare(""" + RETURN + $intList, + $stringList + ; + """) + let result = try conn.execute_(stmt, [ + "intList": [1, 2, 3], + "stringList": ["hi", "there"], + ]) + + let tuple = try #require(try result.getNext()) + #expect(try tuple[0, as: [Double].self] == [1, 2, 3]) + #expect(try tuple[1, as: [String].self] == ["hi", "there"]) + } + + @Test + func testExecuteStruct() throws { + let stmt = try conn.prepare(""" + RETURN + $intStruct, + $stringStruct + ; + """) + let result = try conn.execute_(stmt, [ + "intStruct": ["1": 1, "2": 2], + "stringStruct": ["1": "1", "2": "2"] + ]) + + let tuple = try #require(try result.getNext()) + #expect(try tuple[0, as: [String: Double].self] == ["1": 1, "2": 2]) + #expect(try tuple[1, as: [String: String].self] == ["1": "1", "2": "2"]) + } + + @Test + func testExecuteMap() throws { + let stmt = try conn.prepare(""" + RETURN + $map1, + $map2 + ; + """) + let result = try conn.execute_(stmt, [ + "map1": KuzuMap([("1", 1), ("2", 2)]), + "map2": KuzuMap([("1", "1"), ("2", "2")]) + ]) + + let tuple = try #require(try result.getNext()) + let map1 = try tuple[0, as: [(String, Double)].self] + #expect(map1.map(\.0) == ["1", "2"]) + #expect(map1.map(\.1) == [1, 2]) + let map2 = try tuple[1, as: KuzuMap.self] + #expect(map2.tuples.map(\.0) == ["1", "2"]) + #expect(map2.tuples.map(\.1) == ["1", "2"]) + } +} From 4d3c3687a6046279d77d953cd8bf7c6d41f0aa1f Mon Sep 17 00:00:00 2001 From: hiimtmac Date: Fri, 26 Sep 2025 14:00:10 -0500 Subject: [PATCH 3/5] remaining types --- Sources/Kuzu/KuzuDecodable.swift | 179 +++++++++++++++++++++++++--- Sources/Kuzu/Types+Extensions.swift | 30 +++++ 2 files changed, 195 insertions(+), 14 deletions(-) diff --git a/Sources/Kuzu/KuzuDecodable.swift b/Sources/Kuzu/KuzuDecodable.swift index 4f97355c..44c9f29a 100644 --- a/Sources/Kuzu/KuzuDecodable.swift +++ b/Sources/Kuzu/KuzuDecodable.swift @@ -653,6 +653,115 @@ extension KuzuNode_: KuzuDecodable { } } +@_spi(Typed) +extension KuzuRelationship_: KuzuDecodable { +public static let kuzuDataTypes: [KuzuDataType] = [.rel] + public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var idValue = kuzu_value() + let idState = kuzu_rel_val_get_id_val(container.ptr, &idValue) + let idKuzu = KuzuValue(ptr: &idValue) + guard idState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get node ID with status: \(idState)" + ) + } + let id = try KuzuInternalId.kuzuDecode(from: idKuzu) + + var sourceValue = kuzu_value() + let sourceState = kuzu_rel_val_get_src_id_val(container.ptr, &sourceValue) + let sourceKuzu = KuzuValue(ptr: &sourceValue) + guard sourceState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get relationship source ID with status: \(sourceState)" + ) + } + let sourceId = try KuzuInternalId.kuzuDecode(from: sourceKuzu) + + var targetValue = kuzu_value() + let targetState = kuzu_rel_val_get_dst_id_val(container.ptr, &idValue) + let targetKuzu = KuzuValue(ptr: &targetValue) + guard targetState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get relationship target ID with status: \(targetState)" + ) + } + let targetId = try KuzuInternalId.kuzuDecode(from: targetKuzu) + + var labelValue = kuzu_value() + let labelState = kuzu_rel_val_get_label_val(container.ptr, &labelValue) + let labelKuzu = KuzuValue(ptr: &labelValue) + guard labelState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get relationship label with status: \(labelState)" + ) + } + let label = try String.kuzuDecode(from: labelKuzu) + + let propertySize = try container.kuzuRelElementCount() + var properties: [String: any KuzuDecodable] = [:] + + for i in UInt64(0).. Self { + try container.nullCheck() + try container.typeCheck(Self.kuzuDataTypes) + + var nodesValue = kuzu_value() + let nodesState = kuzu_value_get_recursive_rel_node_list( + container.ptr, + &nodesValue + ) + let nodesKuzu = KuzuValue(ptr: &nodesValue) + guard nodesState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get recursive relationship nodes with status: \(nodesState)" + ) + } + + var relsValue = kuzu_value() + let relsState = kuzu_value_get_recursive_rel_rel_list( + container.ptr, + &relsValue + ) + let relsKuzu = KuzuValue(ptr: &relsValue) + guard relsState == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get recursive relationship relationships with status: \(relsState)" + ) + } + + let nodes = try Array.kuzuDecode(from: nodesKuzu) + let rels = try Array.kuzuDecode(from: relsKuzu) + + return KuzuRecursiveRelationship_( + nodes: nodes, + relationships: rels + ) + } +} + @_spi(Typed) public struct KuzuAnyDecodable: KuzuDecodable { public let value: any KuzuDecodable @@ -664,6 +773,22 @@ public struct KuzuAnyDecodable: KuzuDecodable { let decoded = try type.id.decode(from: container) return .init(value: decoded) } + + public static func kuzuUnion(from container: consuming KuzuValue) throws -> Self { + try container.nullCheck() + try container.typeCheck([.union]) + + var unionValue = kuzu_value() + let state = kuzu_value_get_struct_field_value(container.ptr, 0, &unionValue) + let kuzu = KuzuValue(ptr: &unionValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get union value with status: \(state)" + ) + } + + return try Self.kuzuDecode(from: kuzu) + } } // MARK: Helpers @@ -762,6 +887,32 @@ extension KuzuValue { } return String(cString: currentKey) } + + fileprivate func getRelName(index: UInt64) throws -> String { + var currentKey: UnsafeMutablePointer? + let keyState = kuzu_rel_val_get_property_name_at(ptr, index, ¤tKey) + defer { kuzu_destroy_string(currentKey) } + guard keyState == KuzuSuccess, let currentKey else { + throw KuzuError.valueConversionFailed( + "Failed to get rel propery name with status: \(keyState)" + ) + } + return String(cString: currentKey) + } + + fileprivate func getRelValue( + index: UInt64, + into cValue: inout kuzu_value + ) throws -> KuzuValue { + let state = kuzu_rel_val_get_property_value_at(ptr, index, &cValue) + let kuzu = KuzuValue(ptr: &cValue) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get node property value with status: \(state)" + ) + } + return kuzu + } fileprivate func kuzuListElementCount() throws -> UInt64 { var numElements: UInt64 = 0 @@ -813,6 +964,19 @@ extension KuzuValue { return propertySize } + + fileprivate func kuzuRelElementCount() throws -> UInt64 { + var propertySize: UInt64 = 0 + + let state = kuzu_rel_val_get_property_size(ptr, &propertySize) + guard state == KuzuSuccess else { + throw KuzuError.valueConversionFailed( + "Failed to get number of elements in relationship with status: \(state)" + ) + } + + return propertySize + } } extension LogicalType { @@ -831,17 +995,4 @@ extension LogicalType { return numElements } -} - -#warning("Implement me") -// switch logicalTypeId { -// case KUZU_UNION: -// return try kuzuUnionValueToSwiftValue(&cValue) -// case KUZU_NODE: -// return try kuzuNodeValueToSwiftNode(&cValue) -// case KUZU_REL: -// return try kuzuRelValueToSwiftRelationship(&cValue) -// case KUZU_RECURSIVE_REL: -// return try kuzuRecursiveRelValueToSwiftRecursiveRelationship(&cValue) -// ) -// } +} \ No newline at end of file diff --git a/Sources/Kuzu/Types+Extensions.swift b/Sources/Kuzu/Types+Extensions.swift index abba26c1..995c397d 100644 --- a/Sources/Kuzu/Types+Extensions.swift +++ b/Sources/Kuzu/Types+Extensions.swift @@ -76,6 +76,30 @@ public struct KuzuNode_ { public let properties: [String: any KuzuDecodable] } +@_spi(Typed) +public struct KuzuRelationship_ { + /// The internal ID of the relationship + public let id: KuzuInternalId + /// The internal ID of the source node. + public let sourceId: KuzuInternalId + /// The internal ID of the target node. + public let targetId: KuzuInternalId + /// The label of the relationship. + public let label: String + /// The properties of the relationship, where keys are property names and values are property values. + public let properties: [String: any KuzuDecodable] +} + +/// Represents a recursive relationship retrieved from a path query in Kuzu. +/// A recursive relationship has a list of nodes and a list of relationships. +@_spi(Typed) +public struct KuzuRecursiveRelationship_ { + /// The list of nodes in the recursive relationship. + public let nodes: [KuzuNode_] + /// The list of relationships in the recursive relationship. + public let relationships: [KuzuRelationship_] +} + // Tuples cannot conform to protocols so we need this wrapper @_spi(Typed) public struct KuzuMap { @@ -124,6 +148,9 @@ public struct KuzuDataType: Equatable, Sendable { public static let map = KuzuDataType(id: KUZU_MAP) public static let `struct` = KuzuDataType(id: KUZU_STRUCT) public static let node = KuzuDataType(id: KUZU_NODE) + public static let rel = KuzuDataType(id: KUZU_REL) + public static let recursiveRel = KuzuDataType(id: KUZU_RECURSIVE_REL) + public static let union = KuzuDataType(id: KUZU_UNION) public static let any = KuzuDataType(id: KUZU_ANY) func decode(from container: consuming KuzuValue) throws -> any KuzuDecodable { @@ -151,6 +178,9 @@ public struct KuzuDataType: Equatable, Sendable { case .map: try Dictionary.kuzuDecode(from: container) case .struct:try Dictionary.kuzuDecode(from: container) case .node: try KuzuNode_.kuzuDecode(from: container) + case .rel: try KuzuRelationship_.kuzuDecode(from: container) + case .recursiveRel: try KuzuRecursiveRelationship_.kuzuDecode(from: container) + case .union: try KuzuAnyDecodable.kuzuUnion(from: container) default: throw KuzuError.valueConversionFailed("Unsupported type: \(self)") } From 3f89b66955ed11315b968fd97f5e2f1a19a0550e Mon Sep 17 00:00:00 2001 From: hiimtmac Date: Fri, 26 Sep 2025 15:09:28 -0500 Subject: [PATCH 4/5] fix copilot suggestions --- Sources/Kuzu/KuzuDecodable.swift | 12 ++++++------ Sources/Kuzu/Types+Extensions.swift | 3 +-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Sources/Kuzu/KuzuDecodable.swift b/Sources/Kuzu/KuzuDecodable.swift index 44c9f29a..0699bb3f 100644 --- a/Sources/Kuzu/KuzuDecodable.swift +++ b/Sources/Kuzu/KuzuDecodable.swift @@ -655,7 +655,7 @@ extension KuzuNode_: KuzuDecodable { @_spi(Typed) extension KuzuRelationship_: KuzuDecodable { -public static let kuzuDataTypes: [KuzuDataType] = [.rel] + public static let kuzuDataTypes: [KuzuDataType] = [.rel] public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { try container.nullCheck() try container.typeCheck(Self.kuzuDataTypes) @@ -681,7 +681,7 @@ public static let kuzuDataTypes: [KuzuDataType] = [.rel] let sourceId = try KuzuInternalId.kuzuDecode(from: sourceKuzu) var targetValue = kuzu_value() - let targetState = kuzu_rel_val_get_dst_id_val(container.ptr, &idValue) + let targetState = kuzu_rel_val_get_dst_id_val(container.ptr, &targetValue) let targetKuzu = KuzuValue(ptr: &targetValue) guard targetState == KuzuSuccess else { throw KuzuError.valueConversionFailed( @@ -723,7 +723,7 @@ public static let kuzuDataTypes: [KuzuDataType] = [.rel] @_spi(Typed) extension KuzuRecursiveRelationship_: KuzuDecodable { -public static let kuzuDataTypes: [KuzuDataType] = [.recursiveRel] + public static let kuzuDataTypes: [KuzuDataType] = [.recursiveRel] public static func kuzuDecode(from container: consuming KuzuValue) throws -> Self { try container.nullCheck() try container.typeCheck(Self.kuzuDataTypes) @@ -882,7 +882,7 @@ extension KuzuValue { defer { kuzu_destroy_string(currentKey) } guard keyState == KuzuSuccess, let currentKey else { throw KuzuError.valueConversionFailed( - "Failed to get node propery name with status: \(keyState)" + "Failed to get node property name with status: \(keyState)" ) } return String(cString: currentKey) @@ -894,7 +894,7 @@ extension KuzuValue { defer { kuzu_destroy_string(currentKey) } guard keyState == KuzuSuccess, let currentKey else { throw KuzuError.valueConversionFailed( - "Failed to get rel propery name with status: \(keyState)" + "Failed to get rel property name with status: \(keyState)" ) } return String(cString: currentKey) @@ -995,4 +995,4 @@ extension LogicalType { return numElements } -} \ No newline at end of file +} diff --git a/Sources/Kuzu/Types+Extensions.swift b/Sources/Kuzu/Types+Extensions.swift index 995c397d..1319240b 100644 --- a/Sources/Kuzu/Types+Extensions.swift +++ b/Sources/Kuzu/Types+Extensions.swift @@ -157,7 +157,6 @@ public struct KuzuDataType: Equatable, Sendable { switch self { case .bool: try Bool.kuzuDecode(from: container) case .serial, .int64: try Int64.kuzuDecode(from: container) - case .int64: try Int64.kuzuDecode(from: container) case .uint64: try UInt64.kuzuDecode(from: container) case .int32: try Int32.kuzuDecode(from: container) case .uint32: try UInt32.kuzuDecode(from: container) @@ -176,7 +175,7 @@ public struct KuzuDataType: Equatable, Sendable { case .blob: try Data.kuzuDecode(from: container) case .list, .array: try Array.kuzuDecode(from: container) case .map: try Dictionary.kuzuDecode(from: container) - case .struct:try Dictionary.kuzuDecode(from: container) + case .struct: try Dictionary.kuzuDecode(from: container) case .node: try KuzuNode_.kuzuDecode(from: container) case .rel: try KuzuRelationship_.kuzuDecode(from: container) case .recursiveRel: try KuzuRecursiveRelationship_.kuzuDecode(from: container) From d23bafa30a192a43ea6afe9fa42bb87ccd888f56 Mon Sep 17 00:00:00 2001 From: hiimtmac Date: Fri, 26 Sep 2025 16:52:06 -0500 Subject: [PATCH 5/5] revert package.swift changes --- Package.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Package.swift b/Package.swift index 7d951d23..190311bc 100644 --- a/Package.swift +++ b/Package.swift @@ -1,14 +1,8 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription -let swiftSettings: [SwiftSetting] = [ - /// https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md - /// Require `any` for existential types. - .enableUpcomingFeature("ExistentialAny"), -] - let package = Package( name: "kuzu-swift", platforms: [ @@ -29,8 +23,7 @@ let package = Package( // Targets can depend on other targets in this package and products from dependencies. .target( name: "Kuzu", - dependencies: ["cxx-kuzu"], - swiftSettings: swiftSettings + dependencies: ["cxx-kuzu"] ), .target( name: "cxx-kuzu",