import Foundation
import SQLite
struct SQLiteDecoder: Decoder {
let statement: Statement
let bindings: [Binding?]
let codingPath: [CodingKey] = []
let userInfo: [CodingUserInfoKey: Any] = [:]
func container<Key: CodingKey>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> {
KeyedDecodingContainer(SQLiteKeyedDecodingContainer(statement: statement, bindings: bindings))
}
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding an unkeyed container is not supported"))
}
func singleValueContainer() throws -> SingleValueDecodingContainer {
SingleValueDecoder(statement: statement, bindings: bindings)
}
}
struct SingleValueDecoder: SingleValueDecodingContainer {
let statement: Statement
let bindings: [Binding?]
let codingPath: [CodingKey] = []
func decodeNil() -> Bool {
bindings[0] == nil
}
func decode<T: Decodable>(_ type: T.Type) throws -> T {
guard let binding = bindings[0] else {
throw DecodingError.valueNotFound(T.self, DecodingError.Context(codingPath: codingPath, debugDescription: "Single value was not found"))
}
return try binding.decode(type)
}
}
struct SQLiteKeyedDecodingContainer<Key: CodingKey>: KeyedDecodingContainerProtocol {
let statement: Statement
let bindings: [Binding?]
let codingPath: [CodingKey] = []
var allKeys: [Key] {
statement.columnNames.compactMap { Key(stringValue: $0) }
}
func contains(_ key: Key) -> Bool {
statement.columnNames.contains(key.stringValue)
}
func decodeNil(forKey key: Key) throws -> Bool {
try getBinding(key) == nil
}
func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable {
try getBinding(key, type).decode(type)
}
func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding nested containers is not supported"))
}
func nestedUnkeyedContainer(forKey key: Key) throws -> any UnkeyedDecodingContainer {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding unkeyed containers is not supported"))
}
func superDecoder() throws -> any Decoder {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding super decoders is not supported"))
}
func superDecoder(forKey key: Key) throws -> any Decoder {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: codingPath, debugDescription: "Decoding super decoders is not supported"))
}
func getBinding(_ key: CodingKey) throws -> Binding? {
guard let index = statement.columnNames.firstIndex(of: key.stringValue) else {
throw DecodingError.keyNotFound(key, DecodingError.Context(codingPath: codingPath, debugDescription: "Column \(key.stringValue) was not found"))
}
return bindings[index]
}
func getBinding<T>(_ key: CodingKey, _ type: T.Type) throws -> Binding {
guard let binding = try getBinding(key) else {
throw DecodingError.valueNotFound(type, DecodingError.Context(codingPath: codingPath, debugDescription: "Value \(key.stringValue) was not found"))
}
return binding
}
}
extension Binding {
func decode<T>(_ type: T.Type) throws -> T {
switch type {
case is Bool.Type:
try Bool(decode() as Bool) as! T
case is String.Type:
try String(decode() as String) as! T
case is Double.Type:
try Double(decode() as Double) as! T
case is Float.Type:
try Float(decode() as Double) as! T
case is Int.Type:
try Int(decode() as Int64) as! T
case is Int8.Type:
try Int8(decode() as Int64) as! T
case is Int16.Type:
try Int16(decode() as Int64) as! T
case is Int32.Type:
try Int32(decode() as Int64) as! T
case is Int64.Type:
try Int64(decode() as Int64) as! T
case is UInt.Type:
try UInt(decode() as Int64) as! T
case is UInt8.Type:
try UInt8(decode() as Int64) as! T
case is UInt16.Type:
try UInt16(decode() as Int64) as! T
case is UInt32.Type:
try UInt32(decode() as Int64) as! T
case is UInt64.Type:
try UInt64(decode() as Int64) as! T
case let type2 as any RawRepresentable.Type:
try decodeRawRepresentable(type2) as! T
case let type2 as any Decodable.Type:
try decodeDecodable(type2) as! T
default:
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Cannot decode \(type)"))
}
}
func decode<T>() throws -> T {
guard let value = self as? T else {
throw DecodingError.typeMismatch(T.self, DecodingError.Context(codingPath: [], debugDescription: "Cannot decode \(self) as \(T.self)"))
}
return value
}
func decodeRawRepresentable<T: RawRepresentable<R>, R>(_ type: T.Type) throws -> T {
do {
guard let result = try type.init(rawValue: decode() as R) else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Cannot decode raw representable"))
}
return result
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Cannot decode raw representable", underlyingError: error))
}
}
func decodeDecodable<T: Decodable>(_ type: T.Type) throws -> T {
do {
let json = try decode() as String
return try JSONDecoder().decode(type, from: json.data(using: .utf8) ?? Data())
} catch {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "Cannot decode JSON", underlyingError: error))
}
}
}
import Foundation
import SQLite
extension Connection {
func query(_ query: String) throws -> Connection {
let _ = try self.queryHelper(query, [])
return self
}
func query<T: Codable>(_ query: String) throws -> T? {
try self.queryHelper(query, []).decodeFirst()
}
func query<T: Codable>(_ query: String) throws -> [T] {
try self.queryHelper(query, []).decodeAll()
}
//
func query(_ query: String, _ arguments: [Codable?]) throws -> Connection {
let _ = try self.queryHelper(query, arguments)
return self
}
func query<T: Codable>(_ query: String, _ arguments: [Codable?]) throws -> T? {
try self.queryHelper(query, arguments).decodeFirst()
}
func query<T: Codable>(_ query: String, _ arguments: [Codable?]) throws -> [T] {
try self.queryHelper(query, arguments).decodeAll()
}
//
private func queryHelper(_ query: String, _ arguments: [Codable?]) throws -> Statement {
let bindings: [Binding?] = try arguments.map {
switch $0 {
case .none:
Binding?.none
case .some(let value):
switch value {
case let value2 as Bool:
value2
case let value2 as Int:
value2
case let value2 as Int64:
value2
case let value2 as Double:
value2
case let value2 as String:
value2
// case let value2 as Blob:
// value2
case let value2 as any RawRepresentable<Bool>:
value2.rawValue
case let value2 as any RawRepresentable<Int>:
value2.rawValue
case let value2 as any RawRepresentable<Int64>:
value2.rawValue
case let value2 as any RawRepresentable<Double>:
value2.rawValue
case let value2 as any RawRepresentable<String>:
value2.rawValue
case let value2 as any RawRepresentable<Blob>:
value2.rawValue
default:
try String(data: JSONEncoder().encode(value), encoding: .utf8)
}
}
}
return try self.run(query, bindings)
}
}
extension Statement {
func decodeFirst<T: Codable>() throws -> T? {
try self.first(where: { _ in true })?.decode(self)
}
func decodeAll<T: Codable>() throws -> [T] {
try self.map { try $0.decode(self) }
}
}
extension [Binding?] {
func decode<T: Codable>(_ statement: Statement) throws -> T {
try T(from: SQLiteDecoder(statement: statement, bindings: self))
}
}
usage
try db.query("PRAGMA user_version") as Int
try db.query("SELECT * FROM users WHERE id = ?", [id]) as [User]
try db.query("UPDATE users SET name=? WHERE id=?", [name, id]).changes
check the code
usage