前言sql
入坑 wcdb 有兩個月了,總體來講仍是很不錯的,具體優勢能夠參考文檔說明,因爲官方明確說明不支持 SQL 只好本身寫一個擴展支持一下了 😂swift
聲明
歡迎轉載,但請保留文章原始出處:)
博客園:http://www.cnblogs.com
農民伯伯: http://over140.cnblogs.comapp
正文oop
1、功能實現spa
fork 一份源碼而後把下面代碼加入源碼(有些類限制了訪問做用域)code
SelectSQL.swiftorm
import Foundation extension Database { public func prepareSelectSQL(on propertyConvertibleList: [PropertyConvertible], sql: String, values: [ColumnEncodableBase] = []) throws -> SelectSQL { return try SelectSQL(with: self, on: propertyConvertibleList, sql: sql, values: values) } } public final class SelectSQL { private final var core: Core final var optionalRecyclableHandleStatement: RecyclableHandleStatement? final var statement: StatementSelectSQL private let keys: [CodingTableKeyBase] private let values: [ColumnEncodableBase] private lazy var decoder = TableDecoder(keys, on: optionalRecyclableHandleStatement!) init(with core: Core, on propertyConvertibleList: [PropertyConvertible], sql: String, values: [ColumnEncodableBase]) throws { //TODO: Use generic to check all coding table keys conform to same root type keys = propertyConvertibleList.asCodingTableKeys() self.statement = StatementSelectSQL(sql: sql) self.core = core self.values = values } private func bindValues() throws { guard values.count > 0 else { return } let handleStatement = try lazyHandleStatement() for idx in 0..<values.count { handleStatement.bind(values[idx].archivedFundamentalValue(), toIndex: idx + 1) } } deinit { try? finalize() } /// Get all selected objects according to the `CodingTableKey`. /// /// - Returns: Table decodable objects according to the `CodingTableKey` /// - Throws: `Error` public func allObjects() throws -> [Any] { let rootType = keys[0].rootType as? TableDecodableBase.Type assert(rootType != nil, "\(keys[0].rootType) must conform to TableDecodable protocol.") var objects: [Any] = [] try bindValues() while try next() { objects.append(try rootType!.init(from: decoder)) } return objects } /// Get all selected objects. /// /// - Parameter type: Type of table decodable object /// - Returns: Table decodable objects. /// - Throws: `Error` public func allObjects<Object: TableDecodable>(of type: Object.Type = Object.self) throws -> [Object] { assert(keys is [Object.CodingKeys], "Properties must belong to \(Object.self).CodingKeys.") var objects: [Object] = [] try bindValues() while try next() { objects.append(try Object.init(from: decoder)) } return objects } final func finalize() throws { if let recyclableHandleStatement = optionalRecyclableHandleStatement { try recyclableHandleStatement.raw.finalize() optionalRecyclableHandleStatement = nil } } final func lazyHandleStatement() throws -> HandleStatement { if optionalRecyclableHandleStatement == nil { optionalRecyclableHandleStatement = try core.prepare(statement) } return optionalRecyclableHandleStatement!.raw } //Since `next()` may throw errors, it can't conform to `Sequence` protocol to fit a `for in` loop. @discardableResult public final func next() throws -> Bool { do { return try lazyHandleStatement().step() } catch let error { try? finalize() throw error } } } extension SelectSQL: CoreRepresentable { /// The tag of the related database. public final var tag: Tag? { return core.tag } /// The path of the related database. public final var path: String { return core.path } }
StatementSelectSQL.swiftblog
import Foundation public final class StatementSelectSQL: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .select } public init(sql: String) { self.description = sql } }
UpdateSQL.swiftip
import Foundation extension Database { public func prepareUpdateSQL(sql: String) throws -> UpdateSQL { return try UpdateSQL(with: self, sql: sql) } } /// The chain call for updating public final class UpdateSQL { private var core: Core private let statement: StatementUpdateSQL /// The number of changed rows in the most recent call. /// It should be called after executing successfully public var changes: Int? init(with core: Core, sql: String) throws { self.core = core self.statement = StatementUpdateSQL(sql: sql) } /// Execute the update chain call with row. /// /// - Parameter row: Column encodable row /// - Throws: `Error` public func execute(with row: [ColumnEncodableBase?] = []) throws { let recyclableHandleStatement: RecyclableHandleStatement = try core.prepare(statement) let handleStatement = recyclableHandleStatement.raw for (index, value) in row.enumerated() { let bindingIndex = index + 1 handleStatement.bind(value?.archivedFundamentalValue(), toIndex: bindingIndex) } try handleStatement.step() changes = handleStatement.changes } } extension UpdateSQL: CoreRepresentable { /// The tag of the related database. public var tag: Tag? { return core.tag } /// The path of the related database. public var path: String { return core.path } }
StatementUpdateSQL.swift 作用域
import Foundation public final class StatementUpdateSQL: Statement { public private(set) var description: String = "" public var statementType: StatementType { return .update } public init(sql: String) { self.description = sql } }
2、使用 SQL 查詢或更新數據
2.1 查詢
database.prepareSelectSQL(User.Properties.Id, "SELECT id FROM users where id = ?", values: ["1"])
須要特別注意的是若是返回 Codable 數據,SELECT 字段的順序必需要和 CodingKeys 裏的順序一致,不然數據會填充亂,可是用 WINQ 不會有這個問題。
2.2 更新
let updateSQL = try database.prepareUpdateSQL(sql: "UPDATE conversations SET last_message_id = (select id from messages where conversation_id = ? order by created_at DESC limit 1) WHERE conversation_id = ?") try updateSQL.execute(with: [conversationId, conversationId])
結束
目前用了一段時間沒有發現什麼問題,除了前面那個注意順序問題,WINQ 就是拼 SQL 語句搞不懂官方爲啥不直接支持一個,就算能支持全部 SQL 改起來也很麻煩,並且代碼量不少。