Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Notifications You must be signed in to change notification settings

vickhello/sql-build

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

30 Commits

Repository files navigation

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

upgrade

go get -u 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

使用说明

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","zzz").
		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基本一致,只是把过滤替换成了抛出异常,这是对于有些业务需要指定的条件,而该条件可能为空的情况,做出的判断

WhereFunc

WhereFunc条件能自动过滤WhereFunc("toTimeUUID('2017-10-27 01:00+0000')", "tid > ").这种函数的调用,在结果两边不会自动加上''

func TestBug3(t *testing.T) {
 sql, err := sqlBuild.Select("permission ").
 	Column("service", "operation").
 	WhereFunc("toTimeUUID('2017-10-27 01:00+0000')", "tid > ").
 	String()
 if err != nil {
 	t.Error(err.Error())
 }
 t.Log(sql)
}

sql输出

SELECT service,operation FROM permission WHERE tid > toTimeUUID('2017-10-27 01:00+0000')
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
  • Option(options ...string) InsertInf
  • NoOption(noOptions...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为自增主键,如果使用了mycat中间件来设置自动增长,需要在标注了auto的情况下再标注mycat标签auto;mycat:next value for MYCATSEQ_AGENT,如下面例子 注意3:如果使用了option或者noOption方法,请按调用顺序放在value和values前面

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)
type Tab struct {
	Id int `insert:"id;auto;mycat:next value for MYCATSEQ_AGENT"`
	Name string `insert:"name"`
	Age int `insert:"age"`
}
func TestMycat(t *testing.T) {
	debug.Debug = true
	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)
}

sql打印:

INSERT INTO xx(id,name,age) VALUES (next value for MYCATSEQ_AGENT,'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)
Option 只insert指定选项
func TestOption(t *testing.T) {
	debug.Debug = true
	var tab1 = Tab{Id: 0, Name: "yiersan", Age: 18}
	sql, err := sqlBuild.Insert("xx").
		Option("name").
		Value(&tab1).String()
	if err != nil {
		t.Error(err)
	}
	t.Log(sql)
}

sql打印

INSERT INTO xx(name) VALUES ('yiersan') 
NoOption 不insert指定选项
func TestNoOption(t *testing.T) {
	debug.Debug = true
	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").
		NoOption("name").
		Values(tabs).OrUpdate().String()
	if err != nil {
		t.Error(err)
	}
	t.Log(sql)
}

sql打印

INSERT INTO xx(id,age) VALUES (NULL,18),(NULL,18),(NULL,16) ON DUPLICATE KEY UPDATE id = values(id),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文件夹下的测试文件中找到

常见错误对照表
错误 错误原因 相关函数
The tabName can not be empty meiy 没有给表名 Select(),Update(),Insert(),Delete()
The Value not have need type 传的值不是预期的类型 Value(),Values(),Set(),Set_(),Where_(),Where()
Injection err 可能会有注入危险 all
Not Found Update Data 没有需要更新的数据 Set(),Set_()
Fail to meet the condition 不符合过滤条件 Where_()
Fail to meet the set 不符合过滤条件 Set_()
Need 'Offset' and 'Limit' are used together offset方法必须配合limit方法使用 Offset()
Not found Insert Column 没有找到insert的列名 tag insert:"xxx"
Not found Insert Data 没有找到要insert的数据 没有要insert的数据

About

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

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

AltStyle によって変換されたページ (->オリジナル) /