sql-build,一个支持条件控制的go语言sql拼接库 - Go语言中文社区

sql-build,一个支持条件控制的go语言sql拼接库


项目地址https://github.com/golyu/sql-build

sql-build

sql-build是一个支持条件控制的go语言sql拼接库.共分为4个部分,select,insert,update和delete四个部分,生成的结果为完整sql语句,需要和beego,xorm以及其它支持原生sql语句的orm配置使用,sql-build只做拼接工作.

download

go get github.com/golyu/sql-build

主要功能有
- 无序的链式拼接
- 支持string类型的值注入检测
- 通过条件控制,决定数据有效性 (可以自定条件,用户可以自定义自己的规则,来组合sql-build,这个可以参考sql-build-example项目中models/build目录中的写法,进行组合设置)
- 不支持sql预编译(这个既是优点也是缺点,优点是,可以在不支持预编译语句的sql中间件上使用,缺点就是预编译的优点)

需要注意的是:
- 目前只支持单表操作,不支持联表
- 参数的值在前,列名在后(这个用起来有点别扭,主要是为了使用idea的template)
- 可能缺少一些sql关键字,如果有需要,可以pull request
- 不支持sql预编译

解决的痛点

如果的models中,我们组合sql的时候,还需要判断一些参数是否存在,我们使用拼接库是这样的:

func UpdateUser(id int, username, sex, name string, state int, money float64, qq, email string) error {
    if id > 0 {
        set := ""
        if len(username) > 0 {
            set += " name = '" + username + "'"
        }
        if len(sex) > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " sex = '" + sex + "'"
        }
        if len(name) > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " name = '" + name + "'"
        }
        if state > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " state = " + strconv.Itoa(state)
        }
        if money > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " money = " + fmt.Sprintf("%g",money)
        }
        if len(qq) > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " qq = '" + qq + "'"
        }
        if len(email) > 0 {
            if len(set) > 0 {
                set += ","
            }
            set += " email = '" + email + "'"
        }
        if len(set) > 0 {
            set = " SET" + set
        } else {
            return errors.New("not need update column")
        }
        sql := fmt.Sprintf("UPDATE user "+set+" WHERE id = %d",
            id)
        o:=orm.NewOrm()
        _, err := o.Raw(sql).Exec()
        return err
    }
    return errors.New("Id can not <=0")
}

每一步一个判断,还要考虑数据的类型,一不小心就会出错,下面使用sql-build来重构上述代码

func UpdateUser(id int, username, sex, name string, state int, money float64, qq, email string) error {
    sql, err := sqlBuild.Update("user").
        Where_(id, "id").
        Set(username, "username").
        Set(sex, "sex").
        Set(name, "name").
        Set(state, "state").
        Set(money, "money").
        Set(qq, "qq").
        Set(email, "email").
        String()
    if err != nil {
        return err
    }
    o := orm.NewOrm()
    _, err = o.Raw(sql).Exec()
    return err
}

一下子,就简洁了很多,还不用类型转换,不担心写错,如果配合idea的template功能一起使用,你会感觉更简单,晒一张图
image

使用说明

go
import (
"github.com/golyu/sql-build/debug"
)
sqlBuild.Debug() //开启debug模式,可以看到错误和警告的打印

select

select功能可以支持以下函数,除了Select和String函数放在语句的头尾处,其余的都可以无序设置
- Select(table string) SelectInf
- Column(column string) SelectInf
- Where(value interface{}, key string, rules … Rule) SelectInf
- Where_(value interface{}, key string, rules … Rule) SelectInf
- Like(value string, key string) SelectInf
- In(values interface{}, key string) SelectInf
- NotIn(values interface{}, key string) SelectInf
- OrderBy(orderBy string) SelectInf
- Limit(limit int) SelectInf
- Offset(offset int) SelectInf
- GroupBy(groupBy string) SelectInf
- String() (string, error)

String 生成sql的函数
import (
    "testing"
    "github.com/golyu/sql-build"
)

func TestTable(t *testing.T) {
    myTab:="myTab"
    sql, err := sqlBuild.Select(myTab).String()
    if err != nil {
        t.Error(err.Error())
    }
    t.Log(sql)
    tableName = string(i)
}

输出sql会是:

SELECT * FROM myTab

如果把myTab置为空,err则打印

The tabName can not be empty

以下示例为了简洁,不显示import

Column 查询结果显示列
func TestColumn(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        Column("aaa").
        Column("bbb as xx").
        Column("").
        String()
    if err != nil {
        t.Error(err.Error())
    }
    t.Log(sql)
}

sql打印

SELECT aaa,bbb as xx FROM myTab

空的数据无法通过数据有效性检测,不予组装(这个数据有效性条件是可以控制的)

Where条件控制语句
func TestWhere(t *testing.T) {
    //
    sql, err := sqlBuild.Select("myTab").
        Where("nameValue", "name").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where("nameValue", "name").
        Where(12, "age > ").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where("nameValue", "name").
        Where(12, "age > ").
        Where(0, "phone ").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where("nameValue", "name").
        Where(12, "age > ").
        Where("", "address").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输入结果为:

    SELECT * FROM myTab WHERE name = 'nameValue'
    SELECT * FROM myTab WHERE name = 'nameValue' and age > 12
    SELECT * FROM myTab WHERE name = 'nameValue' and age > 12
    SELECT * FROM myTab WHERE name = 'nameValue' and age > 12

对于key中没有带>=<符号的,sql-build会自定补上=.
第三,四,明明有些条件存在,为什么生成的sql后丢失了呢,这就是sql-build的条件控制功能了,默认的数值类型<=0的条件,string类型值为”“的都会被过滤掉,也就是数据无效,如果你说,我们的业务里也有值为0的啊,怎么办,sql-build可以自定义数据有效性过滤条件,在后面会详细说明.

Where_ 条件控制语句
func TestWhere_(t *testing.T) {
    //
    sql, err := sqlBuild.Select("myTab").
        Where_("nameValue", "name").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where_("nameValue", "name").
        Where_(12, "age > ").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where_("nameValue", "name").
        Where_(12, "age > ").
        Where_(0, "phone ").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Where_("nameValue", "name").
        Where_(12, "age > ").
        Where_("", "address").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出结果为:

SELECT * FROM myTab WHERE name = 'nameValue'
SELECT * FROM myTab WHERE name = 'nameValue' and age > 12

err输出结果为:

Fail to meet the condition
Fail to meet the condition

可以看出,Where_功能上和Where基本一致,只是把过滤替换成了抛出异常,这是对于有些业务需要指定的条件,而该条件可能为空的情况,做出的判断

GroupBy
func TestGroupBy(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        GroupBy("aa").
        GroupBy("bb").
        GroupBy("").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出

SELECT * FROM myTab GROUP BY aa,bb
OrderBy
func TestOrderBy(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        OrderBy("aa1").
        OrderBy("-bb1").
        OrderBy("").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出为:

SELECT * FROM myTab ORDER BY aa1,-bb1

正序直接写入列名,倒序在前加上-号

Limit Offset
func TestLimit(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        Limit(10).
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Limit(10).
        Offset(2).
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Offset(2).
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出为:

SELECT * FROM myTab LIMIT 10
SELECT * FROM myTab LIMIT 10 OFFSET 2

err输出为:

Need 'Offset' and 'Limit' are used together

Limit可以单独使用,Offset需要配合Offset使用,可以先Offset再Limit,我们中间的功能语句无序,只需要出现过就行

In
func TestIn(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        In([]string{"小明", "小华"}, "name").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        In([]string{"小明", "小华"}, "name").
        In([]int{12, 22, 33, 16}, "age").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        In("小花", "name").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出:

SELECT * FROM myTab WHERE name IN ('小明','小华')
SELECT * FROM myTab WHERE name IN ('小明','小华') and age IN (12,22,33,16)

err输出:

The Value not have need type

in语句的值不允许为基本类型切片外的其它任何类型,包括基础类型,上述语句可以改写为

sql, err = sqlBuild.Select("myTab").
    In([]string{"小花"}, "name").
    String()
NotIn
func TestNotin(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        NotIn([]string{"小明", "小华"}, "name").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        NotIn([]string{"小明", "小华"}, "name").
        NotIn([]int{12, 22, 33, 16}, "age").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        NotIn("小花", "name").
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}
Like
func TestLike(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        Like("我的家", "address").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Like("%我的家", "address").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //
    sql, err = sqlBuild.Select("myTab").
        Like("我的家%", "address").String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出为:

SELECT * FROM myTab WHERE address like '%我的家%'
SELECT * FROM myTab WHERE address like '%我的家'
SELECT * FROM myTab WHERE address like '我的家%'

如果like的值没有%,自动在前后补上%

以上就是sql-build中select功能的大致使用,功能块的函数是可以混合使用的,上面的举例只是部分写法,那我们合起来写一次

func TestAll(t *testing.T) {
    sql, err := sqlBuild.Select("myTab").
        Where("一班", "class").
        Where(0, "age>").
        Where("c", "").
        Where_("男", "sex").
        In([]string{"语文", "数学"}, "hobby").
        NotIn([]int{6, 7}, "xx").
        GroupBy("xxx").
        GroupBy("xxxx").
        OrderBy("-id").
        Limit(10).
        Offset(2).String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
    //injection
    sql, err = sqlBuild.Select("myTab").
        Where("一班", "class").
        Where(0, "age>").
        Where("c", "").
        Where_("男' and 0<>(select count(*) from myTab) and ''=''", "sex").
        In([]string{"语文", "数学"}, "hobby").
        NotIn([]int{6, 7}, "xx").
        GroupBy("xxx").
        GroupBy("xxxx").
        OrderBy("-id").
        Limit(10).
        Offset(2).
        String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}

sql输出:

SELECT * FROM myTab WHERE class = '一班' and sex = '男' and hobby IN ('语文','数学') and xx NOT IN (6,7) GROUP BY xxx,xxxx ORDER BY -id LIMIT 10 OFFSET 2

err输出:

Injection err

检查出string类型的值里面包含注入的关键字

insert

insert功能支持以下函数

  • Insert(table string) InsertInf
  • Value(value interface{}, rules … Rule) InsertInf
  • Values(value interface{}, rules … Rule) InsertInf
  • OrUpdate()InsertInf
  • String() (string, error)

注意1:insert方法因为需要在insert的方法上面加上insert的tag,value和values方法不可以同时使用
注意2:如果数据库中有自增主键,请在tags中用auto标出,使用;隔开,一个struct中只可以标记一个为auto为自增主键,如下面例子

value
type Tab struct {
    Id   int `insert:"id;auto"`
    Name string `insert:"name"`
    Age  int `insert:"age"`
}

func TestValue(t *testing.T) {
    var tab = Tab{Id: 0, Name: "yiersan", Age: 18}
    sql, err := sqlBuild.Insert("xx").
        Value(&tab).String()
    if err != nil {
        t.Error(err)
    }
    t.Log(sql)
}

首先,给filed加上insert的tag,然后调用value,传入指针,同select一样,value方法也可以自定义规则传入使用.
sql打印:

INSERT INTO xx(id,name,age) VALUES (DEFAULT,'yiersan',18)
values 批量插入
func TestValues(t *testing.T) {
    var tab1 = Tab{Id: 0, Name: "yiersan", Age: 18}
    var tab2 = Tab{Id: 0, Name: "xx", Age: 16}
    var tab3 = Tab{Id: 0, Name: "pp", Age: 18}
    var tabs = []Tab{tab1, tab2, tab3}
    sql, err := sqlBuild.Insert("xx").
        Values(tabs).String()
    if err != nil {
        t.Error(err)
    }
    t.Log(sql)
}

sql打印:

INSERT INTO xx(id,name,age) VALUES (DEFAULT,'pp',18),(DEFAULT,'xx',16),(DEFAULT,'yiersan',18)

这里的批量插入在源码中的反射异步了,所以插入多条和插入单条,理论时间大致是一样的

OrUpdate 不存在就插入,存在就更新
func TestOrUpdate(t *testing.T) {
    var tab1 = Tab{Id: 0, Name: "yiersan", Age: 18}
    var tab2 = Tab{Id: 0, Name: "xx", Age: 16}
    var tab3 = Tab{Id: 0, Name: "pp", Age: 18}
    var tabs = []Tab{tab1, tab2, tab3}
    sql, err := sqlBuild.Insert("xx").
        Values(tabs).
        OrUpdate().
        String()
    if err != nil {
        t.Error(err)
    }
    t.Log(sql)
}

sql打印:

INSERT INTO xx(id,name,age) VALUES (DEFAULT,'xx',16),(DEFAULT,'yiersan',18),(DEFAULT,'pp',18)  ON DUPLICATE KEY UPDATE id = values(id),name = values(name),age = values(age)

Delete

delete功能支持以下函数

  • Delete(table string) DeleteInf
  • Where(value interface{}, key string, rules … Rule) DeleteInf
  • Where_(value interface{}, key string, rules … Rule) DeleteInf
  • Like(value string, key string) DeleteInf
  • In(values interface{}, key string) DeleteInf
  • NotIn(values interface{}, key string) DeleteInf
  • OrderBy(orderBy string) DeleteInf
  • Limit(limit int) DeleteInf
  • Offset(offset int) DeleteInf
  • GroupBy(groupBy string) DeleteInf
  • String() (string, error)

delete功能基本支持select功能的所有函数和用法,column除外
我们还是写一个例子

func TestDeleteAll(t *testing.T) {
    sql, err := sqlBuild.Delete("myTab").
        Where("一班", "class").
        Where(0, "age>").
        Where("c", "").
        Where_("男", "sex").
        In([]string{"语文", "数学"}, "hobby").
        NotIn([]int{6, 7}, "xx").
        GroupBy("xxx").
        GroupBy("xxxx").
        OrderBy("-id").
        Limit(10).
        Offset(2).String()
    if err != nil {
        t.Error(err.Error())
        err = nil
    }
    t.Log(sql)
}   

sql打印:

DELETE FROM myTab WHERE class = '一班' and sex = '男' and hobby IN ('语文','数学') and xx NOT IN (6,7) GROUP BY xxx,xxxx ORDER BY -id LIMIT 10 OFFSET 2

可能delete主要用到的也只有Where,Where_,In,NotIn功能的,其它的有些鸡肋,不过既然sql支持的,我们也就尽量支持,至少提供一个可选项

Update

update功能也是在select功能基础上改造,去掉了column,添加了setset_函数

  • Update(table string) UpdateInf
  • Set(value interface{}, key string, rules … Rule) UpdateInf
  • Set_(value interface{}, key string, rules … Rule) UpdateInf
  • Where(value interface{}, key string, rules … Rule) UpdateInf
  • Where_(value interface{}, key string, rules … Rule) UpdateInf
  • Like(value string, key string) UpdateInf
  • In(values interface{}, key string) UpdateInf
  • NotIn(values interface{}, key string) UpdateInf
  • OrderBy(orderBy string) UpdateInf
  • Limit(limit int) UpdateInf
  • Offset(offset int) UpdateInf
  • GroupBy(groupBy string) UpdateInf
  • String() (string, error)

同样,我们也写个示例

func TestUpdateWhere(t *testing.T) {
    sql, err := sqlBuild.Update("myTab").
        Set("二中", "school").
        Where("nameValue", "name").String()
    if err != nil {
            t.Error(err.Error())
            err = nil
        }
        t.Log(sql)
    sql, err = sqlBuild.Update("myTab").
            Set_("", "school").
            Where("nameValue", "name").
            Where(12, "age > ").String()
        if err != nil {
            t.Error(err.Error())
            err = nil
        }
        t.Log(sql)
}

sql打印:

UPDATE myTab SET school = '二中' WHERE name = 'nameValue'

错误打印:

Fail to meet the set

有个朋友问了个问题,可能是我的说明没写清楚,问题是这样的,确实需要把一个值update成空字符串,怎么办呢,
如果只是少量语句需要这样,可以使用以下的方法,局部条件过滤,只要设置的值不是*****,就不会报Fail to meet the set错误

func TestSetEmpty(t *testing.T) {
    sql, err := sqlBuild.Update("myTable").
        Where_("123", "id").
        Set_("", "name", sqlBuild.Rule{StringValue: "*****"}).
        String()
    if err != nil {
        t.Error(err.Error())
    }
    t.Log(sql)
}

sql打印

UPDATE myTable SET name = '' WHERE id = '123'

set_,where_,这种后面带下划线的都表强制,如果不符合过滤条件,就会返回错误,而不带下划线的set,where都是跳过不合条件的数据

以上的详细示例都可以在test文件夹下的测试文件中找到

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/u012296101/article/details/76236220
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-25 15:52:58
  • 阅读 ( 1178 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢