forked from vapor/sqlite-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSQLiteDataEncoder.swift
More file actions
208 lines (176 loc) · 8.25 KB
/
Copy pathSQLiteDataEncoder.swift
File metadata and controls
208 lines (176 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#if hasFeature(Embedded)
import SQLKit
import SQLiteNIO
/// Translates a bound query parameter (a ``SQLKit/SQLBindValue``) into an `SQLiteData` value.
///
/// The Codable/JSON path used on normal platforms is unavailable in Embedded Swift, so on that target
/// values are mapped directly from their driver-neutral ``SQLKit/SQLDataValue`` representation.
public struct SQLiteDataEncoder: Sendable {
/// Initialize a ``SQLiteDataEncoder``.
public init() {}
/// Convert the given bound value to an `SQLiteData` value.
///
/// - Parameter value: The value to convert.
/// - Returns: The converted `SQLiteData` value.
public func encode(_ value: any SQLBindValue & Sendable) throws -> SQLiteData {
switch value.sqlDataValue {
case .integer(let int): return .integer(SQLiteInt64(int))
case .double(let double): return .float(double)
case .string(let string): return .text(string)
case .blob(let bytes): return .blob(bytes)
case .bool(let bool): return .integer(bool ? 1 : 0)
case .null: return .null
}
}
}
#else
#if !NativeConcurrency
import NIOCore
#endif
import Foundation
@_spi(CodableUtilities) import SQLKit
import SQLiteNIO
/// Translates `Encodable` values into `SQLiteData` values suitable for use with an `SQLiteDatabase`.
///
/// Types which conform to `SQLiteDataConvertible` are converted directly to `SQLiteData`. Other types are
/// encoded as JSON and sent to the database as text.
public struct SQLiteDataEncoder: Sendable {
/// A wrapper to silence `Sendable` warnings for `JSONEncoder` when not on macOS.
struct FakeSendable<T>: @unchecked Sendable { let value: T }
/// The `JSONEncoder` used for encoding values that can't be directly converted.
let json: FakeSendable<JSONEncoder>
/// Initialize a ``SQLiteDataEncoder`` with an unconfigured JSON encoder.
public init() {
self.init(json: .init())
}
/// Initialize a ``SQLiteDataEncoder`` with a JSON encoder.
///
/// - Parameter json: A `JSONEncoder` to use for encoding types that can't be directly converted.
public init(json: JSONEncoder) {
self.json = .init(value: json)
}
/// Convert the given `Encodable` value to an `SQLiteData` value, if possible.
///
/// - Parameter value: The value to convert.
/// - Returns: A converted `SQLiteData` value, if successful.
public func encode(_ value: any Encodable) throws -> SQLiteData {
if let data = (value as? any SQLiteDataConvertible)?.sqliteData {
return data
} else {
let encoder = NestedSingleValueUnwrappingEncoder(dataEncoder: self)
do {
try value.encode(to: encoder)
guard let value = encoder.value else {
throw SQLCodingError.unsupportedOperation("missing value", codingPath: [])
}
return value
} catch is SQLCodingError {
// Starting with SQLite 3.45.0 (2024-01-15), sending textual JSON as a blob will cause inexplicable
// errors due to the data being interpreted as JSONB (arguably not the best behavior for SQLite's API,
// but not technically a compatibility break). As there is no good way to get at the underlying SQLite
// version from the data encoder, and extending `SQLiteData` would make a rather epic mess, we now just
// always send JSON as text instead. This is technically what we should have been doing all along
// anyway, meaning this change is a bugfix. Good thing, too - otherwise we'd be stuck trying to retain
// bug-for-bug compatibility, starting with reverse-engineering SQLite's JSONB format (which is not the
// same as PostgreSQL's, of course).
//
// Update: SQLite 3.45.1 (2024-01-30) fixed the JSON-blob behavior, but as noted above, we prefer
// sending JSON as text anyway, so we've left it as-is.
return .text(.init(decoding: try self.json.value.encode(value), as: UTF8.self))
}
}
}
/// A trivial encoder for unwrapping types which encode as trivial single-value containers. This allows for
/// correct handling of types such as `Optional` when they do not conform to `SQLiteDataConvertible`.
private final class NestedSingleValueUnwrappingEncoder: Encoder, SingleValueEncodingContainer {
// See `Encoder.userInfo`.
var userInfo: [CodingUserInfoKey: Any] { [:] }
// See `Encoder.codingPath` and `SingleValueEncodingContainer.codingPath`.
var codingPath: [any CodingKey] { [] }
/// The parent ``SQLiteDataEncoder``.
let dataEncoder: SQLiteDataEncoder
/// Storage for the resulting converted value.
var value: SQLiteData? = nil
/// Create a new encoder with an ``SQLiteDataEncoder``.
init(dataEncoder: SQLiteDataEncoder) {
self.dataEncoder = dataEncoder
}
// See `Encoder.container(keyedBy:)`.
func container<K: CodingKey>(keyedBy: K.Type) -> KeyedEncodingContainer<K> {
.invalid(at: self.codingPath)
}
// See `Encoder.unkeyedContainer`.
func unkeyedContainer() -> any UnkeyedEncodingContainer {
.invalid(at: self.codingPath)
}
// See `Encoder.singleValueContainer`.
func singleValueContainer() -> any SingleValueEncodingContainer {
self
}
// See `SingleValueEncodingContainer.encodeNil()`.
func encodeNil() throws {
self.value = .null
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Bool) throws {
self.value = .integer(value ? 1 : 0)
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: String) throws {
self.value = .text(value)
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Float) throws {
self.value = .float(Double(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Double) throws {
self.value = .float(value)
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int8) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int16) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int32) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int64) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: Int) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt8) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt16) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt32) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt64) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: UInt) throws {
self.value = .integer(numericCast(value))
}
// See `SingleValueEncodingContainer.encode(_:)`.
func encode(_ value: some Encodable) throws {
self.value = try self.dataEncoder.encode(value)
}
}
}
#endif // hasFeature(Embedded)