sqlbuilder(三)
序
复工回来,除了第一天比较清闲之外,后续的工作稍显忙碌
忙碌的需求告一段落,今天继续来整个活
老样子,先上个文章合集 [SqlDriver]
今天呢,准备把sql生成这里收收尾,处理一下自动的update和select 参数生成
目标
- 终极目标
自动 update struct 填入 set 部分
自动拆解一个 struct 转换成为where条件
自动 一个带有pk的结构体
- 过程
- 拆解struct 实现update语句转化
- 拆解struct 实现where条件转化
- 定义新tag,识别出主键,进行完全自动的update
磨刀
老样子,先磨刀
先把根据结构体获取表名的函数提取出来,这里面用到的地方会比较多
func TableName(i interface{}) string {
var tName string
if t, ok := i.(Table); ok {
tName = t.TableName()
} else {
//未实现接口,取struct名称的蛇形
tName = stringx.SnakeName(reflectx.StructName(i))
}
return tName
}
接着提取一下获取列名的函数
func ColumnName(t reflect.StructField) string {
column := t.Tag.Get("db")
if len(column) == 0 {
//入无自定义column,取field名称的蛇形
column = stringx.SnakeName(t.Name)
}
return column
}
接下来是testCase
func TestTableName(t *testing.T) {
s := struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
}{}
type TestTable struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
asda bool
}
s1 := TestTable{}
s2 := TestTable{
AaaAa: "asdasdasdasdas",
BbbBb: 200,
CccCc: 3.14,
asda: true,
}
t.Log(TableName(s))
t.Log(TableName(s1))
t.Log(TableName(s2))
}
func TestColumnName(t *testing.T) {
s := struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
}{}
reflectx.StructRange(s, func(f reflect.StructField, v reflect.Value) error {
t.Log(ColumnName(f))
return nil
})
}
以及,testCase的运行结果
=== RUN TestTableName
utils_test.go:32:
utils_test.go:33: test_table
utils_test.go:34: test_table
--- PASS: TestTableName (0.00s)
PASS
=== RUN TestColumnName
utils_test.go:45: a
utils_test.go:45: bbb_bb
utils_test.go:45: ccc_cc
--- PASS: TestColumnName (0.00s)
PASS
自动拆解struct的工具我们基本上已经写好了,但是从struct到Where的转换,目前还是没有
并且由于 struct到where条件的转换,在本次的三个目标都比较重要,所以放在磨刀环节
上代码
func StructToWhere(i interface{}) (where string, whereArgs []interface{}) {
var (
whereList []string
)
_ = reflectx.StructRange(i, func(t reflect.StructField, v reflect.Value) error {
//获取tag,也就是自定义的column
column := ColumnName(t)
// "-" 表示忽略,空数据 也直接跳过
if column == "-" || reflectx.IsNull(v) {
return nil
}
whereList = append(whereList, fmt.Sprintf("%s = ?", column))
whereArgs = append(whereArgs, v.Interface())
return nil
})
where = strings.Join(whereList, " and ")
return
}
继续testCase
func TestStructToWhere(t *testing.T) {
s := struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
}{}
type TestTable struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
asda bool
}
s1 := TestTable{}
s2 := TestTable{
AaaAa: "asdasdasdasdas",
BbbBb: 200,
CccCc: 3.14,
asda: true,
}
t.Log(StructToWhere(s))
t.Log(StructToWhere(s1))
t.Log(StructToWhere(s2))
}
以及,运行结果
=== RUN TestStructToWhere
utils_test.go:73: []
utils_test.go:74: []
utils_test.go:75: a = ? and bbb_bb = ? and ccc_cc = ? [asdasdasdasdas 200 3.14]
--- PASS: TestStructToWhere (0.00s)
PASS
杀猪
首先呢,我们把半自动的update先安排上
func HalfAutoUpdate(set interface{}, where interface{}) (SqlBuilder, error) {
//老样子 先拿表名来生成一个sqlbuilder
tName := TableName(set)
if len(tName) == 0 {
return nil, errors.New("can not find table name")
}
sb := UpdateFrom(tName)
ic := 0 //有效列计数
_ = reflectx.StructRange(set, func(t reflect.StructField, v reflect.Value) error {
//获取tag,也就是自定义的column
column := ColumnName(t)
// "-" 表示忽略,空数据 也直接跳过
if column == "-" || reflectx.IsNull(v) {
return nil
}
// 书接上回,进行sql构建(单一属性)
sb.Set(column, v.Interface())
ic++
return nil
})
//计数器为0表示无可用列构建,返回错误
if ic == 0 {
return nil, errors.New("no column field to sql")
}
whereSql, whereArgs := StructToWhere(where)
if len(whereSql) > 0 {
sb.Where(whereSql, whereArgs...)
}
return sb, nil
}
这样的半自动基本上已经ok,虽然半自动也挺好,但是我还是希望进行全自动的处理,尤其是进行了两次的结构体遍历,让人十分不爽
所以我准备加入一个tag 叫做pk,当我们识别到pk==1的时候 主动把他作为主键update的条件
所以在structRange的时候,获取完StructField,就开始进行分道扬镳了
先加入识别pk 的函数
func IsPk(t reflect.StructField) bool {
tag := t.Tag.Get("pk")
if len(tag) == 0 {
//入无自定义column,取field名称的蛇形
return false
}
return tag == "1"
}
以及这个函数的testCase
func TestIsPk(t *testing.T) {
s := struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64 `pk:"1"`
}{}
reflectx.StructRange(s, func(f reflect.StructField, v reflect.Value) error {
t.Log(f.Name, IsPk(f))
return nil
})
}
以及testCase的执行结果
=== RUN TestIsPk
utils_test.go:86: AaaAa false
utils_test.go:86: BbbBb false
utils_test.go:86: CccCc true
--- PASS: TestIsPk (0.00s)
PASS
由此,我们的全自动 update 就诞生了
func AutoUpdate(i interface{}) (SqlBuilder, error) {
//老样子 先拿表名来生成一个sqlbuilder
tName := TableName(i)
if len(tName) == 0 {
return nil, errors.New("can not find table name")
}
sb := UpdateFrom(tName)
ic := 0 //有效列计数
_ = reflectx.StructRange(i, func(t reflect.StructField, v reflect.Value) error {
//获取tag,也就是自定义的column
column := ColumnName(t)
if IsPk(t) {
sb.Where(fmt.Sprintf("%s = ?", column), v.Interface())
} else {
// "-" 表示忽略,空数据 也直接跳过
if column == "-" || reflectx.IsNull(v) {
return nil
}
// 书接上回,进行sql构建(单一属性)
sb.Set(column, v.Interface())
ic++
}
return nil
})
//计数器为0表示无可用列构建,返回错误
if ic == 0 {
return nil, errors.New("no column field to sql")
}
return sb, nil
}
然后搞一下它的testCase
func TestAutoUpdate(t *testing.T) {
type TestTable struct {
AaaAa string `db:"a"`
BbbBb int
CccCc float64
asda bool
Id string `pk:"1"`
}
s := TestTable{
AaaAa: "asdasdasdasdas",
BbbBb: 200,
CccCc: 3.14,
asda: true,
}
sql, err := AutoUpdate(s)
if err != nil {
t.Error(err.Error())
return
} else {
t.Log(sql.Sql(), sql.Args())
}
}
运行结果
=== RUN TestAutoUpdate
sql_test.go:105: update from test_table set a = ?,bbb_bb = ?,ccc_cc = ? where id = ? [asdasdasdasdas 200 3.14 ]
--- PASS: TestAutoUpdate (0.00s)
PASS
后记
至此,今天的目标已经完成的差不多了,难得贴了这么多代码,已经正经的不像是我了
本期应该可以说是自打使用Hugo建立bolg以来,代码贴的最多的一期了,所以今天这个活,是不是。。