Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New SQL Coders #1262

Open
pravdomil opened this issue Apr 16, 2024 · 1 comment
Open

New SQL Coders #1262

pravdomil opened this issue Apr 16, 2024 · 1 comment

Comments

@pravdomil
Copy link

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 pravdomil changed the title New SQL Decoder New SQL Coders Apr 16, 2024
@pravdomil
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant