|
8 | 8 | import Foundation |
9 | 9 | import CoreModel |
10 | 10 | import SQLite |
11 | | -#if canImport(Darwin) |
12 | | -import SQLite3 |
13 | | -#elseif canImport(SQLiteSwiftCSQLite) |
14 | | -import SQLiteSwiftCSQLite |
15 | | -#elseif canImport(CSQLite) |
16 | | -import CSQLite |
17 | | -#else |
18 | | -import SQLite3 |
19 | | -#endif |
20 | | - |
21 | | -// Registers custom scalar functions by calling `sqlite3_create_function_v2` directly |
22 | | -// with `@convention(c)` function pointers, rather than SQLite.swift's `createFunction`. |
23 | | -// SQLite.swift registers the callback with a `@convention(block)` closure cast to a raw |
24 | | -// pointer, which is unreliable off Apple platforms (upstream |
25 | | -// https://github.com/stephencelis/SQLite.swift/issues/1071). A plain C function pointer |
26 | | -// plus a retained context pointer works identically on every platform. |
27 | | - |
28 | | -/// The SQLite `SQLITE_TRANSIENT` sentinel destructor, telling SQLite to copy a result |
29 | | -/// value immediately (it is a macro in C, so it isn't imported). |
30 | | -private let transientDestructor = unsafeBitCast(-1, to: sqlite3_destructor_type.self) |
31 | | - |
32 | | -/// Retains a ``DatabaseFunction`` so it can be passed through SQLite as an opaque pointer |
33 | | -/// and recovered inside the C callback. |
34 | | -private final class FunctionBox { |
35 | | - let function: DatabaseFunction |
36 | | - init(_ function: DatabaseFunction) { self.function = function } |
37 | | -} |
38 | 11 |
|
39 | 12 | internal extension SQLite.Connection { |
40 | 13 |
|
41 | | - /// Registers a `DatabaseFunction` with this connection via the SQLite C API. |
| 14 | + /// Registers a `DatabaseFunction` as a custom SQL scalar function. |
42 | 15 | func register(function: DatabaseFunction) throws { |
43 | | - let box = Unmanaged.passRetained(FunctionBox(function)) |
44 | | - let flags = SQLITE_UTF8 | (function.deterministic ? SQLITE_DETERMINISTIC : 0) |
45 | | - let argumentCount = function.argumentCount.map { Int32($0) } ?? -1 |
46 | | - let code = sqlite3_create_function_v2( |
47 | | - handle, |
| 16 | + let evaluate = function.evaluate |
| 17 | + try createFunction( |
48 | 18 | function.name, |
49 | | - argumentCount, |
50 | | - flags, |
51 | | - box.toOpaque(), |
52 | | - { context, argc, argv in |
53 | | - let function = Unmanaged<FunctionBox>.fromOpaque(sqlite3_user_data(context)).takeUnretainedValue().function |
54 | | - var arguments = [AttributeValue?]() |
55 | | - arguments.reserveCapacity(Int(argc)) |
56 | | - for index in 0..<Int(argc) { |
57 | | - arguments.append(argumentValue(argv?[index])) |
58 | | - } |
59 | | - setResult(context, function.evaluate(arguments)) |
60 | | - }, |
61 | | - nil, // xStep (scalar function, no aggregate) |
62 | | - nil, // xFinal |
63 | | - { pointer in |
64 | | - // Balance `passRetained` when SQLite drops the function. |
65 | | - guard let pointer else { return } |
66 | | - Unmanaged<FunctionBox>.fromOpaque(pointer).release() |
| 19 | + argumentCount: function.argumentCount.map { Int32($0) }, |
| 20 | + deterministic: function.deterministic |
| 21 | + ) { arguments in |
| 22 | + let values: [AttributeValue?] = arguments.map { AttributeValue(functionArgument: $0) } |
| 23 | + guard let result = evaluate(values), let binding = result.binding else { |
| 24 | + return .null |
67 | 25 | } |
68 | | - ) |
69 | | - guard code == SQLITE_OK else { |
70 | | - box.release() // xDestroy isn't called when registration fails |
71 | | - throw SQLiteDatabaseError.unableToCreateFunction(function.name, code) |
| 26 | + return binding |
72 | 27 | } |
73 | 28 | } |
74 | 29 | } |
75 | 30 |
|
76 | | -/// Read a SQLite argument value into an ``AttributeValue``, inferring its shape from the |
77 | | -/// value's runtime storage class. |
78 | | -private func argumentValue(_ value: OpaquePointer?) -> AttributeValue { |
79 | | - switch sqlite3_value_type(value) { |
80 | | - case SQLITE_INTEGER: |
81 | | - return .int64(sqlite3_value_int64(value)) |
82 | | - case SQLITE_FLOAT: |
83 | | - return .double(sqlite3_value_double(value)) |
84 | | - case SQLITE_TEXT: |
85 | | - guard let text = sqlite3_value_text(value) else { return .null } |
86 | | - return .string(String(cString: text)) |
87 | | - case SQLITE_BLOB: |
88 | | - guard let bytes = sqlite3_value_blob(value) else { return .data(Data()) } |
89 | | - return .data(Data(bytes: bytes, count: Int(sqlite3_value_bytes(value)))) |
90 | | - default: |
91 | | - return .null |
92 | | - } |
93 | | -} |
94 | | - |
95 | | -/// Set a function's result on the SQLite context from an ``AttributeValue``. |
96 | | -private func setResult(_ context: OpaquePointer?, _ value: AttributeValue?) { |
97 | | - guard let value, let binding = value.binding else { |
98 | | - sqlite3_result_null(context) |
99 | | - return |
100 | | - } |
101 | | - switch binding { |
102 | | - case let integer as Int64: |
103 | | - sqlite3_result_int64(context, integer) |
104 | | - case let double as Double: |
105 | | - sqlite3_result_double(context, double) |
106 | | - case let text as String: |
107 | | - sqlite3_result_text(context, text, -1, transientDestructor) |
108 | | - case let blob as Blob: |
109 | | - sqlite3_result_blob(context, blob.bytes, Int32(blob.bytes.count), transientDestructor) |
110 | | - default: |
111 | | - sqlite3_result_null(context) |
| 31 | +private extension AttributeValue { |
| 32 | + |
| 33 | + /// Read a SQL function argument into an ``AttributeValue``, inferring its shape from |
| 34 | + /// the value's storage class. |
| 35 | + init(functionArgument binding: Binding) { |
| 36 | + switch binding { |
| 37 | + case let .integer(value): |
| 38 | + self = .int64(value) |
| 39 | + case let .double(value): |
| 40 | + self = .double(value) |
| 41 | + case let .text(value): |
| 42 | + self = .string(value) |
| 43 | + case .blob: |
| 44 | + self = .data(Data(binding.bytes ?? [])) |
| 45 | + case .null: |
| 46 | + self = .null |
| 47 | + } |
112 | 48 | } |
113 | 49 | } |
0 commit comments