[Swift 4.0] extended WCDB supports SQL statements

Posted by bkanmani on Sun, 05 Apr 2020 22:12:30 +0200

Preface

wcdb has been in the pit for two months, which is still very good on the whole. For specific advantages, please refer to the documentation. Because the official specification does not support SQL, we have to write an extension to support it A kind of

 

statement
Welcome to reprint, but please keep the original source of the article:)
Blog Park: http://www.cnblogs.com
Uncle farmer: http://over140.cnblogs.com

 

text

I. function realization

fork a source code and add the following code to the source code (some classes limit access scope)

  SelectSQL.swift

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.swift

    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.swift

    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. Use SQL to query or update data

2.1 query

      database.prepareSelectSQL(User.Properties.Id, "SELECT id FROM users where id = ?", values: ["1"])

It should be noted that if Codable data is returned, the order of the SELECT field must be the same as that in CodingKeys. Otherwise, the data will be filled disorderly. However, WINQ will not cause this problem.

2.2 update

                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])

  

End

At present, we haven't found any problems in a period of time. In addition to the previous attention to the order problem, WINQ just can't understand why the official doesn't directly support one SQL statement. Even if it can support all SQL statements, it's troublesome to change them, and the code volume is large.

Topics: Swift SQL Database