古老的榕树

Go 优雅的 SQL 语句拼接库

潘军杰 发表于 2018-07-03 23:51 阅读(11009) 评论(0) 赞(1)
大部分时间都在使用数据库的业务操作,对我而言,这一部分的东西很重要,当初选择了很接近于 Go 原生 database/sql 的 sqlx 库,使用习惯了,感觉已经够用了。不过有些项目,让我很怀念 mybatis 这种介于 ORM 的组件库,有时候,我们需要优雅的拼接一个 SQL 语句,而不是使用多个字符串拼接来操作,因为对面特定的 sql 语法和 sqlx 的传参方式和顺序,代码量会增加不少,也让代码变得很丑陋邋遢,后期维护起来,很麻烦很不爽。

自己的需求很明确,不是 ORM 组件,只需一个动态拼接 SQL 语句和参数,不影响实体结构体的定义,最后支持导出 SQL 语句和参数组,这就是一个我心里理想的 SQL 拼接库,自己打算写一个的时候,发现 github 上已经有了,而且比我理想中还要完美和轻量。

sqrl  https://github.com/elgris/sqrl 的介绍如:Fluent SQL generation for golang,很符合 Go 的理念。仔细参详了,发现它是 forked 于 https://github.com/Masterminds/squirrel 的,两项目是父子关系,区别在于线程安全和非线程安全。

根据 sqrl 作者的介绍,SQL 拼接库,一般线程安全并不是完全有必要的,所以适当简化提升了 squirrel,构造出一个非线程安全的 squirrel ,并增加了一些有用的属性,最后出来了 sqrl。我最后也是选择了 sqrl 作为研究对象。


查询语句生成如:

import sq "github.com/elgris/sqrl"

users := sq.Select("*").From("users").Join("emails USING (email_id)")
active := users.Where(sq.Eq{"deleted_at": nil}).
Where("sex=?",1).
Where(sq.Eq{"status":1}).
Where(sq.Eq{"username": []string{"moe", "larry", "curly", "shemp"}}).Offset(0).Limit(10)
query, args, err := active.ToSql()


用对象的方式拼接 SQL 语句,出来的结果:
query == "SELECT * FROM users JOIN emails USING (email_id) WHERE deleted_at IS NULL AND sex=? AND status=? AND username in (?,?,?,?) offset 0 limit 10"

args == [1,1,"moe", "larry", "curly", "shemp",0,10]


这是大体的查询语句的使用,动态 where 语句非常灵活。


如果配合 sqlx 使用如下:

list := []model.User{}
err := db.Select(&list,query,args...)

天生完美配合,两者又非常独立。


插入语句生成如:
query, args, err := sq.
    Insert("users").Columns("name", "age").
    Values("moe", 13).Values("larry", sq.Expr("? + 5", 12)).
    ToSql()

生成 SQL 的语句
query == "INSERT INTO users (name,age) VALUES (?,?),(?,? + 5)"
args == ["moe", 13],["larry", 12]


更新语句生成如:
query, args, err := sq.Update("users").
    SetMap(sq.Eq{"status": 1, "sex": 1, "updated": time.Now()}).
    Set("name", "Jack").
    Where("id=?", 1)

生成 SQL 的语句
query == "UPDATE users SET status=?,sex=?,updated=? where id=?"
args == [1,1,"2018-01-01 12:12:11","Jack", 13]


删除语句生成如:
query, args, err := sq.Delete("users").Where("id=?", 1)

生成 SQL 的语句
query == "DELETE users where id=?"
args == [1]

sqrl 对 mysql 做了一些针对性的函数,删除多张表:
sql, args, err := sq.Delete("a1", "a2").
    From("z1 AS a1").
    JoinClause("INNER JOIN a2 ON a1.id = a2.ref_id").
    Where("b = ?", 1).
    ToSql()

以上语句是关联删除两张表。


MySQL 是全球最流行的开源关系型数据库,以上的语句生成很明显都是只适应 MySQL 的,但 PostgreSQL 同样是全球最高级的开源关系型数据库,也不容小觑,sqrl 对 PostgreSQL 支持也很友好,比如它们的占位符一个是 ?,一个是$, 我们只需要在原来的 builder 基础上,指定占位符即可,比如:

sq.Select("*").From("users").Where("id=?",1).PlaceholderFormat(sq.Dollar) 

同样 Insert Update Delete builder 的使用上 .PlaceholderFormat(sq.Dollar)  同样适用,只需要在一处指定占位符即可,可以搞定占位符$1....$n,顺序带来的困扰。


另外 sqrl 还针对做了一些特殊的函数,适应 PostgreSQL ,比如:json值,数组值的支持:

sql, args, err := sq.Insert("posts").
    Columns("content", "tags").
    Values("Lorem Ipsum", pg.JSONB([]string{"foo", "bar"})).
    ToSql()

sql, args, err := sqrl.Insert("posts").
    Columns("content", "tags").
    Values("Lorem Ipsum", pg.Array([]string{"foo", "bar"})).
    ToSql()

sqrl 原本就是 SQL 生成语句,对数据库层的操作几乎是绝缘的,不要想象她是一个 ORM 组件库,它绝大部分工作只是根据数据库的特征,处理和生成字符串而已。不过也不是和操作数据库绝缘,有一个特殊模块是可以操作数据的,我们可以看看代码使用情况:

stooges := users.Where(sq.Eq{"username": []string{"moe", "larry", "curly", "shemp"}})
three_stooges := stooges.Limit(3)
rows, err := three_stooges.RunWith(db).Query()

实际上以上代码就是完成以下操作:
rows, err := db.Query("SELECT * FROM users WHERE username IN (?,?,?,?) LIMIT 3", "moe", "larry", "curly", "shemp")

我想这个模块,不是很必要的,只是给用户多了一个选择。个人觉得数据库操作上 sqlx 更为专业些,如果你非要图方便使用 sqrl 的数据操作,也无可厚非,选择适合场景的来用。

(完)

0 条网友评论

哇~~~ 竟然还没有评论!

称呼*
邮箱*
内容*
验证码*
验证码 看不清换张