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

Add Coding support #728

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
321 changes: 321 additions & 0 deletions Sources/SQLite/Typed/Query.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
// THE SOFTWARE.
//

import Foundation

public protocol QueryType : Expressible {

var clauses: QueryClauses { get set }
Expand Down Expand Up @@ -637,6 +639,26 @@ extension QueryType {
]).expression)
}

/// Creates an `INSERT` statement by encoding the given object
/// This method converts any custom nested types to JSON data and does not handle any sort
/// of object relationships. If you want to support relationships between objects you will
/// have to provide your own Encodable implementations that encode the correct ids.
///
/// - Parameters:
///
/// - encodable: An encodable object to insert
///
/// - userInfo: User info to be passed to encoder
///
/// - otherSetters: Any other setters to include in the insert
///
/// - Returns: An `INSERT` statement fort the encodable object
public func insert(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Insert {
let encoder = SQLiteEncoder(userInfo: userInfo)
try encodable.encode(to: encoder)
return self.insert(encoder.setters + otherSetters)
}

// MARK: UPDATE

public func update(_ values: Setter...) -> Update {
Expand All @@ -655,6 +677,26 @@ extension QueryType {
return Update(" ".join(clauses.flatMap { $0 }).expression)
}

/// Creates an `UPDATE` statement by encoding the given object
/// This method converts any custom nested types to JSON data and does not handle any sort
/// of object relationships. If you want to support relationships between objects you will
/// have to provide your own Encodable implementations that encode the correct ids.
///
/// - Parameters:
///
/// - encodable: An encodable object to insert
///
/// - userInfo: User info to be passed to encoder
///
/// - otherSetters: Any other setters to include in the insert
///
/// - Returns: An `UPDATE` statement fort the encodable object
public func update(_ encodable: Encodable, userInfo: [CodingUserInfoKey:Any] = [:], otherSetters: [Setter] = []) throws -> Update {
let encoder = SQLiteEncoder(userInfo: userInfo)
try encodable.encode(to: encoder)
return self.update(encoder.setters + otherSetters)
}

// MARK: DELETE

public func delete() -> Delete {
Expand Down Expand Up @@ -1033,6 +1075,13 @@ public struct Row {
self.values = values
}

fileprivate func hasValue(for column: String) -> Bool {
guard let idx = columnNames[column.quote()] else {
return false
}
return values[idx] != nil
}

/// Returns a row’s value for the given column.
///
/// - Parameter column: An expression representing a column selected in a Query.
Expand Down Expand Up @@ -1063,6 +1112,22 @@ public struct Row {
return valueAtIndex(idx)
}

/// Decode an object from this row
/// This method expects any custom nested types to be in the form of JSON data and does not handle
/// any sort of object relationships. If you want to support relationships between objects you will
/// have to provide your own Decodable implementations that decodes the correct columns.
///
/// - Parameter: userInfo
///
/// - Returns: a decoded object from this row
public func decode<V: Decodable>(userInfo: [CodingUserInfoKey: Any] = [:]) throws -> V {
return try V(from: self.decoder(userInfo: userInfo))
}

public func decoder(userInfo: [CodingUserInfoKey: Any] = [:]) -> Decoder {
return SQLiteDecoder(row: self, userInfo: userInfo)
}

// FIXME: rdar://problem/18673897 // subscript<T>…

public subscript(column: Expression<Blob>) -> Blob {
Expand Down Expand Up @@ -1161,3 +1226,259 @@ public struct QueryClauses {
}

}

/// Generates a list of settings for an Encodable object
fileprivate class SQLiteEncoder: Encoder {
struct EncodingError: Error, CustomStringConvertible {
let description: String
}

class SQLiteKeyedEncodingContainer<MyKey: CodingKey>: KeyedEncodingContainerProtocol {
typealias Key = MyKey

let encoder: SQLiteEncoder
let codingPath: [CodingKey] = []

init(encoder: SQLiteEncoder) {
self.encoder = encoder
}

func superEncoder() -> Swift.Encoder {
fatalError("SQLiteEncoding does not support super encoders")
}

func superEncoder(forKey key: Key) -> Swift.Encoder {
fatalError("SQLiteEncoding does not support super encoders")
}

func encodeNil(forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer<Key>.Key) throws {
self.encoder.setters.append(Expression<String?>(key.stringValue) <- nil)
}

func encode(_ value: Int, forKey key: SQLiteEncoder.SQLiteKeyedEncodingContainer<Key>.Key) throws {
self.encoder.setters.append(Expression(key.stringValue) <- value)
}

func encode(_ value: Bool, forKey key: Key) throws {
self.encoder.setters.append(Expression(key.stringValue) <- value)
}

func encode(_ value: Float, forKey key: Key) throws {
self.encoder.setters.append(Expression(key.stringValue) <- Double(value))
}

func encode(_ value: Double, forKey key: Key) throws {
self.encoder.setters.append(Expression(key.stringValue) <- value)
}

func encode(_ value: String, forKey key: Key) throws {
self.encoder.setters.append(Expression(key.stringValue) <- value)
}

func encode<T>(_ value: T, forKey key: Key) throws where T : Swift.Encodable {
if let data = value as? Data {
self.encoder.setters.append(Expression(key.stringValue) <- data)
}
else {
let encoded = try JSONEncoder().encode(value)
self.encoder.setters.append(Expression(key.stringValue) <- encoded)
}
}

func encode(_ value: Int8, forKey key: Key) throws {
throw EncodingError(description: "encoding an Int8 is not supported")
}

func encode(_ value: Int16, forKey key: Key) throws {
throw EncodingError(description: "encoding an Int16 is not supported")
}

func encode(_ value: Int32, forKey key: Key) throws {
throw EncodingError(description: "encoding an Int32 is not supported")
}

func encode(_ value: Int64, forKey key: Key) throws {
throw EncodingError(description: "encoding an Int64 is not supported")
}

func encode(_ value: UInt, forKey key: Key) throws {
throw EncodingError(description: "encoding an UInt is not supported")
}

func encode(_ value: UInt8, forKey key: Key) throws {
throw EncodingError(description: "encoding an UInt8 is not supported")
}

func encode(_ value: UInt16, forKey key: Key) throws {
throw EncodingError(description: "encoding an UInt16 is not supported")
}

func encode(_ value: UInt32, forKey key: Key) throws {
throw EncodingError(description: "encoding an UInt32 is not supported")
}

func encode(_ value: UInt64, forKey key: Key) throws {
throw EncodingError(description: "encoding an UInt64 is not supported")
}

func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey : CodingKey {
fatalError("encoding a nested container is not supported")
}

func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
fatalError("encoding nested values is not supported")
}
}

fileprivate var setters: [SQLite.Setter] = []
let codingPath: [CodingKey] = []
let userInfo: [CodingUserInfoKey: Any]

init(userInfo: [CodingUserInfoKey: Any]) {
self.userInfo = userInfo
}

func singleValueContainer() -> SingleValueEncodingContainer {
fatalError("not supported")
}

func unkeyedContainer() -> UnkeyedEncodingContainer {
fatalError("not supported")
}

func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key : CodingKey {
return KeyedEncodingContainer(SQLiteKeyedEncodingContainer(encoder: self))
}
}

class SQLiteDecoder : Decoder {
struct DecodingError : Error, CustomStringConvertible {
let description: String
}

class SQLiteKeyedDecodingContainer<MyKey: CodingKey> : KeyedDecodingContainerProtocol {
typealias Key = MyKey

let codingPath: [CodingKey] = []
let row: Row

init(row: Row) {
self.row = row
}

var allKeys: [Key] {
return self.row.columnNames.keys.flatMap({Key(stringValue: $0)})
}

func contains(_ key: Key) -> Bool {
return self.row.hasValue(for: key.stringValue)
}

func decodeNil(forKey key: Key) throws -> Bool {
return !self.contains(key)
}

func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
return self.row.get(Expression(key.stringValue))
}

func decode(_ type: Int.Type, forKey key: Key) throws -> Int {
return self.row.get(Expression(key.stringValue))
}

func decode(_ type: Int8.Type, forKey key: Key) throws -> Int8 {
throw DecodingError(description: "decoding an Int8 is not supported")
}

func decode(_ type: Int16.Type, forKey key: Key) throws -> Int16 {
throw DecodingError(description: "decoding an Int16 is not supported")
}

func decode(_ type: Int32.Type, forKey key: Key) throws -> Int32 {
throw DecodingError(description: "decoding an Int32 is not supported")
}

func decode(_ type: Int64.Type, forKey key: Key) throws -> Int64 {
throw DecodingError(description: "decoding an Int64 is not supported")
}

func decode(_ type: UInt.Type, forKey key: Key) throws -> UInt {
throw DecodingError(description: "decoding an UInt is not supported")
}

func decode(_ type: UInt8.Type, forKey key: Key) throws -> UInt8 {
throw DecodingError(description: "decoding an UInt8 is not supported")
}

func decode(_ type: UInt16.Type, forKey key: Key) throws -> UInt16 {
throw DecodingError(description: "decoding an UInt16 is not supported")
}

func decode(_ type: UInt32.Type, forKey key: Key) throws -> UInt32 {
throw DecodingError(description: "decoding an UInt32 is not supported")
}

func decode(_ type: UInt64.Type, forKey key: Key) throws -> UInt64 {
throw DecodingError(description: "decoding an UInt64 is not supported")
}

func decode(_ type: Float.Type, forKey key: Key) throws -> Float {
return Float(self.row.get(Expression<Double>(key.stringValue)))
}

func decode(_ type: Double.Type, forKey key: Key) throws -> Double {
return self.row.get(Expression(key.stringValue))
}

func decode(_ type: String.Type, forKey key: Key) throws -> String {
return self.row.get(Expression(key.stringValue))
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T: Swift.Decodable {
guard let data = self.row.get(Expression<Data?>(key.stringValue)) else {
throw DecodingError(description: "an unsupported type was found")
}
if type == Data.self {
return data as! T
}
return try JSONDecoder().decode(type, from: data)
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
throw DecodingError(description: "decoding nested containers is not supported")
}

func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
throw DecodingError(description: "decoding unkeyed containers is not supported")
}

func superDecoder() throws -> Swift.Decoder {
throw DecodingError(description: "decoding super encoders is not supported")
}

func superDecoder(forKey key: Key) throws -> Swift.Decoder {
throw DecodingError(description: "decoding super encoders is not supported")
}
}

let row: Row
let codingPath: [CodingKey] = []
let userInfo: [CodingUserInfoKey: Any]

init(row: Row, userInfo: [CodingUserInfoKey: Any]) {
self.row = row
self.userInfo = userInfo
}

func container<Key>(keyedBy type: Key.Type) throws -> KeyedDecodingContainer<Key> where Key : CodingKey {
return KeyedDecodingContainer(SQLiteKeyedDecodingContainer(row: self.row))
}

func unkeyedContainer() throws -> UnkeyedDecodingContainer {
throw DecodingError(description: "decoding an unkeyed container is not supported by the SQLiteDecoder")
}

func singleValueContainer() throws -> SingleValueDecodingContainer {
throw DecodingError(description: "decoding a single value is not supported by the SQLiteDecoder")
}
}
Loading