stephencelis / SQLite.swift

A type-safe, Swift-language layer over SQLite3.
MIT License
9.72k stars 1.57k forks source link

New SQL Coders #1262

Open pravdomil opened 6 months ago

pravdomil commented 6 months ago

check the code

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
pravdomil commented 6 months ago

there is no need for Value protocol, there is just RawRepresentable or Codable, simplifies everything a lot