sql 語(yǔ)句Swift封裝,鏈?zhǔn)秸{(diào)用

前言

在開發(fā)中,經(jīng)常需要寫sql語(yǔ)句。但有個(gè)很嚴(yán)重的問題, sql 語(yǔ)句是 字符串型的,書寫容易出錯(cuò)。 所以要封裝sql語(yǔ)句。 我在用代碼一步一步實(shí)現(xiàn)自己的 ios 架構(gòu) 中進(jìn)行了封裝,查看這個(gè)源文件SqlStatement.swift

封裝的兩個(gè)目標(biāo):

  1. 不要手寫字符串
  2. 使用起來簡(jiǎn)單

有兩種方案:
一種使用簡(jiǎn)單,但功能有限
一種使用復(fù)雜,但功能強(qiáng)大
這里先看第一種方案

方案一

分析 sql 語(yǔ)句

SELECT LastName,FirstName FROM Persons
SELECT * FROM Persons WHERE City='Beijing'
SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'
SELECT Company, OrderNumber FROM Orders ORDER BY Company
SELECT * FROM Persons WHERE City LIKE 'N%'
INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson' 
DELETE FROM Person WHERE LastName = 'Wilson' 

Sql語(yǔ)句有四種主要的類型,它們有一些固定的屬性:表名、列名、where條件,order,group等,固定格式。

用類來封裝Sql語(yǔ)句,面向?qū)ο蟮乃枷搿?/p>

  1. 我創(chuàng)建了一下幾個(gè)類。 分別對(duì)應(yīng) 增刪改查Sql 基類 四個(gè)子類 SqlInsert SqlUpdate SqlDelete,SqlSelect為了避免 sql 注入,使用占位符。FMDB 給了兩種方式 ? :colum 這選擇用后者,這兩種方式效果一樣。一開始使用的是 ? 后面發(fā)現(xiàn),? 的對(duì)應(yīng)關(guān)系不好實(shí)現(xiàn)。就改為 冒號(hào)的方式。
  2. 鏈?zhǔn)秸{(diào)用比較自然。只有將方法返回自己就可以實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。

使用

理解了這些,其實(shí)代碼很好實(shí)現(xiàn) 代碼我放在最后。先看一下如何使用:
比如我想實(shí)現(xiàn) SELECT * FROM db.Persons WHERE City='Beijing'

let sql = Sql.select("db").table("Persons").whereStatement("City='Beijing'").build()
print(sql)
# SELECT * FROM Persons WHERE City='Beijing'

因?yàn)槲疫@里用的是多數(shù)據(jù)庫(kù),所以指定了db。
當(dāng)然對(duì)于where有多種寫法,這個(gè)看代碼就明白了。

原理

  1. 鏈?zhǔn)椒椒ㄌ峁┍匾獙傩?/code>
  2. build() 做屬性字符串拼接的工作,組裝成標(biāo)準(zhǔn)sql字符串

優(yōu)缺點(diǎn)

  • 使用簡(jiǎn)單,基本不用理解 sql 語(yǔ)句,鏈?zhǔn)秸{(diào)用的順序并不重要,build()是按照標(biāo)準(zhǔn)sql語(yǔ)句生成的
  • 能應(yīng)對(duì)90%的場(chǎng)景
  • 功能簡(jiǎn)單,無法實(shí)現(xiàn)復(fù)雜的 sql 語(yǔ)句
  • update 時(shí) where 語(yǔ)句的參數(shù) Key 不能相同,不然有bug。

方案二

將Sql語(yǔ)句的每一個(gè)聲明都對(duì)應(yīng)一個(gè)屬性。設(shè)置一個(gè)全局字符串,每調(diào)用一個(gè)方法就拼接一次字符串。這樣就和sql語(yǔ)句一樣強(qiáng)大。但這樣使用達(dá)到了目標(biāo)1。

方案三

你有什么好方案,望賜教!

方案一源碼:

import Foundation

class Sql {
    fileprivate let dbName_:    String
    private(set) var table_:     String? //select 可能是有多個(gè)table 所以 optional
    private(set) var colums_:    [String]?
    
    /// 目前這三個(gè)屬性where 只能同時(shí)滿足一個(gè)
    private(set) var where_:     String?
    private(set) var andWhere_:  [String]?
    private(set) var orWhere_:   [String]?
    
    lazy var realTable: String = {
        return "\(dbName_).\(table_!)"
    }()
    
    init(_ dbName: String) {
        dbName_ = dbName
    }
    
    //    @discardableResult
    static func insert(dbName: String) -> SqlInsert {
        return SqlInsert(dbName)
    }
    
    static func update(dbName: String) -> SqlUpdate {
        return SqlUpdate(dbName)
    }
    
    static func delete(dbName: String) -> SqlDelete {
        return SqlDelete(dbName)
    }
    
    static func select(dbName: String) -> SqlSelect {
        return SqlSelect(dbName)
    }
    
    func table(_ table: String) -> Self {
        table_ = table
        return self;
    }
    
    func colums(_ colums: [String]) -> Self {
        colums_ = colums
        return self
    }
    
    /// 目前這三個(gè) where 只能同時(shí)滿足一個(gè)
    func whereStatement(_ condition: String) -> Self {
        where_ = condition
        return self
    }
    
    func andWhere(_ andWhere: [String]) -> Self {
        andWhere_ = andWhere
        return self
    }
    
    func orWhere(_ orWhere: [String]) -> Self {
        orWhere_ = orWhere
        return self
    }
    
    class func buildWhere() -> String {
        return ""
    }
    
    func buildWhere() -> String {
        var sql :String = String()
        guard let wh = where_ else {
            if let andWhere = andWhere_ {
                sql.append(" where 1 = 1 ")
                _ = andWhere.map {
                    let line = " AND \($0) = :\($0) "
                    sql.append(line)
                }
            }
            if let orWhere = orWhere_ {
                if andWhere_ != nil {
                    
                } else {
                    sql.append(" where 1 = 2 ")
                }
                _ = orWhere.map {
                    let line = " OR \($0) = :\($0) "
                    sql.append(line)
                }
            }
            return sql
        }
        return sql.appending(" \(wh)")
        
    }
    
    func build() -> String {
        return ""
    }
}

final class SqlSelect: Sql {
    private(set) var tables_: [String]?
    private(set) var orderBy_: [(String,String)]?
    private(set) var groupBy_: [String]?
    private(set) var limitOffset: Int?
    private(set) var limitCount: Int?
    
    override lazy var realTable: String = {
        if let ts = tables_ {
            var rt: String = ""
            tables_.map {
                rt.append(contentsOf: "\(dbName_).\($0)  ")
            }
            return rt
        } else {
            return "\(dbName_).\(table_!)"
        }
    }()
    
    func orderBy(_ orderBy: [(String,String)]) -> Self {
        orderBy_ = orderBy
        return self
    }
    
    func groupBy(_ groupBy: [String]) -> Self {
        groupBy_ = groupBy
        return self
    }
    
    func limit(_ offset: Int, _ count: Int) -> Self {
        limitOffset = offset
        limitCount = count
        return self
    }
    
    override func build() -> String {
        var tbs = "*"
        if let cls = colums_ {
            tbs = cls.joined(separator: ",")
        }
        var sql = "SELECT \(tbs) FROM  \(realTable) "
        
        sql.append(buildWhere())
        
        //注:GROUP BY 子句使用時(shí)必須放在 WHERE 子句中的條件之后,必須放在 ORDER BY 子句之前。
        if let gb = groupBy_ {
            sql.append(contentsOf: " Group BY \(gb.joined(separator: ","))")
        }
        
        if let ob = orderBy_ {
            var tmpStr = ""
            _ = ob.map { kv in
                tmpStr.append(contentsOf: "\(kv.0) \(kv.1)")
            }
            sql.append(contentsOf: " ORDER BY \(tmpStr)")
        }
        
        if let offset = limitCount ,let cnt = limitCount {
            sql.append(contentsOf: " LIMIT \(offset) OFFSET \(cnt)")
        }
        
        return sql
    }
}

final class SqlInsert: Sql {
    override func build() -> String {
        var sql = "INSERT INTO \(realTable)"
        if let cls = colums_ {
            var cnames = [String]()
            var values = [String]()
            _ = cls.map {
                cnames.append($0)
                values.append(":\($0)")
            }
            sql.append("(")
            sql.append(cnames.joined(separator: ","))
            sql.append(") values (")
            sql.append(values.joined(separator: ","))
            sql.append(")")
        }
        sql.append(buildWhere())
        return sql;
    }
}

final class SqlUpdate: Sql {
    override func build() -> String {
        var sql = "UPDATE \(realTable) SET "
        if let cls = colums_ {
            _ = cls.map {
                sql.append("\($0) = :\($0) ")
            }
        }
        sql.append(buildWhere())
        return sql;
    }
}

final class SqlDelete: Sql {
    override func build() -> String {
        var sql = "DELETE FROM \(realTable) "
        sql.append(buildWhere())
        return sql;
    }
}

補(bǔ)充

WCDB 騰訊出品,更優(yōu)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容