sql 语句Swift封装,链式调用

前言

在开发中,经常需要写sql语句。但有个很严重的问题, sql 语句是 字符串型的,书写容易出错。 所以要封装sql语句。 我在用代码一步一步实现自己的 ios 架构 中进行了封装,查看这个源文件SqlStatement.swift

封装的两个目标:

  1. 不要手写字符串
  2. 使用起来简单

有两种方案:
一种使用简单,但功能有限
一种使用复杂,但功能强大
这里先看第一种方案

方案一

分析 sql 语句

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语句有四种主要的类型,它们有一些固定的属性:表名、列名、where条件,order,group等,固定格式。

用类来封装Sql语句,面向对象的思想。

  1. 我创建了一下几个类。 分别对应 增删改查Sql 基类 四个子类 SqlInsert SqlUpdate SqlDelete,SqlSelect为了避免 sql 注入,使用占位符。FMDB 给了两种方式 ? :colum 这选择用后者,这两种方式效果一样。一开始使用的是 ? 后面发现,? 的对应关系不好实现。就改为 冒号的方式。
  2. 链式调用比较自然。只有将方法返回自己就可以实现链式调用。

使用

理解了这些,其实代码很好实现 代码我放在最后。先看一下如何使用:
比如我想实现 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'

因为我这里用的是多数据库,所以指定了db
当然对于where有多种写法,这个看代码就明白了。

原理

  1. 链式方法提供必要属性
  2. build() 做属性字符串拼接的工作,组装成标准sql字符串

优缺点

  • 使用简单,基本不用理解 sql 语句,链式调用的顺序并不重要,build()是按照标准sql语句生成的
  • 能应对90%的场景
  • 功能简单,无法实现复杂的 sql 语句
  • update 时 where 语句的参数 Key 不能相同,不然有bug。

方案二

将Sql语句的每一个声明都对应一个属性。设置一个全局字符串,每调用一个方法就拼接一次字符串。这样就和sql语句一样强大。但这样使用达到了目标1。

方案三

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

方案一源码:

import Foundation

class Sql {
    fileprivate let dbName_:    String
    private(set) var table_:     String? //select 可能是有多个table 所以 optional
    private(set) var colums_:    [String]?
    
    /// 目前这三个属性where 只能同时满足一个
    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
    }
    
    /// 目前这三个 where 只能同时满足一个
    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 子句使用时必须放在 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;
    }
}

补充

WCDB 腾讯出品,更优。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269