diff --git a/core/stores/builder/builder.go b/core/stores/builder/builder.go new file mode 100644 index 000000000000..d2fe5cc7b40d --- /dev/null +++ b/core/stores/builder/builder.go @@ -0,0 +1,63 @@ +package builder + +import ( + "fmt" + "reflect" + "strings" +) + +const dbTag = "db" + +// RawFieldNames converts golang struct field into slice string. +func RawFieldNames(in interface{}, postgresSql ...bool) []string { + out := make([]string, 0) + v := reflect.ValueOf(in) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + var pg bool + if len(postgresSql) > 0 { + pg = postgresSql[0] + } + + // we only accept structs + if v.Kind() != reflect.Struct { + panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) + } + + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + // gets us a StructField + fi := typ.Field(i) + if tagv := fi.Tag.Get(dbTag); tagv != "" { + if pg { + out = append(out, tagv) + } else { + out = append(out, fmt.Sprintf("`%s`", tagv)) + } + } else { + if pg { + out = append(out, fi.Name) + } else { + out = append(out, fmt.Sprintf("`%s`", fi.Name)) + } + } + } + + return out +} + +// PostgreSqlJoin concatenates the given elements into a string. +func PostgreSqlJoin(elems []string) string { + b := new(strings.Builder) + for index, e := range elems { + b.WriteString(fmt.Sprintf("%s = $%d, ", e, index+2)) + } + + if b.Len() == 0 { + return b.String() + } + + return b.String()[0 : b.Len()-2] +} diff --git a/core/stores/builder/builder_test.go b/core/stores/builder/builder_test.go new file mode 100644 index 000000000000..cc8a3517b4eb --- /dev/null +++ b/core/stores/builder/builder_test.go @@ -0,0 +1,24 @@ +package builder + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type mockedUser struct { + ID string `db:"id" json:"id,omitempty"` + UserName string `db:"user_name" json:"userName,omitempty"` + Sex int `db:"sex" json:"sex,omitempty"` + UUID string `db:"uuid" uuid:"uuid,omitempty"` + Age int `db:"age" json:"age"` +} + +func TestFieldNames(t *testing.T) { + t.Run("new", func(t *testing.T) { + var u mockedUser + out := RawFieldNames(&u) + expected := []string{"`id`", "`user_name`", "`sex`", "`uuid`", "`age`"} + assert.Equal(t, expected, out) + }) +} diff --git a/go.mod b/go.mod index 14749d19f424..fe974c21ae7b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/go-redis/redis v6.15.9+incompatible github.com/go-sql-driver/mysql v1.6.0 - github.com/go-xorm/builder v0.3.4 github.com/golang-jwt/jwt v3.2.1+incompatible github.com/golang/mock v1.6.0 github.com/google/uuid v1.3.0 diff --git a/go.sum b/go.sum index 0ddc6051f2f1..fb681b02d1d1 100644 --- a/go.sum +++ b/go.sum @@ -154,10 +154,6 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY= -github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw= -github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y= -github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= diff --git a/tools/goctl/model/sql/builderx/builder.go b/tools/goctl/model/sql/builderx/builder.go index be569f7ccefa..2c7db09bf610 100644 --- a/tools/goctl/model/sql/builderx/builder.go +++ b/tools/goctl/model/sql/builderx/builder.go @@ -1,136 +1,20 @@ package builderx import ( - "fmt" - "reflect" - "strings" - - "github.com/go-xorm/builder" + "github.com/tal-tech/go-zero/core/stores/builder" ) -const dbTag = "db" - -// NewEq wraps builder.Eq. -func NewEq(in interface{}) builder.Eq { - return builder.Eq(ToMap(in)) -} - -// NewGt wraps builder.Gt. -func NewGt(in interface{}) builder.Gt { - return builder.Gt(ToMap(in)) -} - -// ToMap converts interface into map. -func ToMap(in interface{}) map[string]interface{} { - out := make(map[string]interface{}) - v := reflect.ValueOf(in) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - // we only accept structs - if v.Kind() != reflect.Struct { - panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) - } - - typ := v.Type() - for i := 0; i < v.NumField(); i++ { - // gets us a StructField - fi := typ.Field(i) - if tagv := fi.Tag.Get(dbTag); tagv != "" { - // set key of map to value in struct field - val := v.Field(i) - zero := reflect.Zero(val.Type()).Interface() - current := val.Interface() - - if reflect.DeepEqual(current, zero) { - continue - } - out[tagv] = current - } - } - - return out -} - -// FieldNames returns field names from given in. -// deprecated: use RawFieldNames instead automatically while model generating after goctl version v1.1.0 +// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builder.RawFieldNames instead. func FieldNames(in interface{}) []string { - out := make([]string, 0) - v := reflect.ValueOf(in) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - // we only accept structs - if v.Kind() != reflect.Struct { - panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) - } - - typ := v.Type() - for i := 0; i < v.NumField(); i++ { - // gets us a StructField - fi := typ.Field(i) - if tagv := fi.Tag.Get(dbTag); tagv != "" { - out = append(out, tagv) - } else { - out = append(out, fi.Name) - } - } - - return out + return builder.RawFieldNames(in) } -// RawFieldNames converts golang struct field into slice string. +// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builder.RawFieldNames instead. func RawFieldNames(in interface{}, postgresSql ...bool) []string { - out := make([]string, 0) - v := reflect.ValueOf(in) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - var pg bool - if len(postgresSql) > 0 { - pg = postgresSql[0] - } - - // we only accept structs - if v.Kind() != reflect.Struct { - panic(fmt.Errorf("ToMap only accepts structs; got %T", v)) - } - - typ := v.Type() - for i := 0; i < v.NumField(); i++ { - // gets us a StructField - fi := typ.Field(i) - if tagv := fi.Tag.Get(dbTag); tagv != "" { - if pg { - out = append(out, tagv) - } else { - out = append(out, fmt.Sprintf("`%s`", tagv)) - } - } else { - if pg { - out = append(out, fi.Name) - } else { - out = append(out, fmt.Sprintf("`%s`", fi.Name)) - } - } - } - - return out + return builder.RawFieldNames(in, postgresSql...) } -// PostgreSqlJoin concatenates the given elements into a string. +// Deprecated: Use github.com/tal-tech/go-zero/core/stores/builderx.PostgreSqlJoin instead. func PostgreSqlJoin(elems []string) string { - b := new(strings.Builder) - for index, e := range elems { - b.WriteString(fmt.Sprintf("%s = $%d, ", e, index+2)) - } - - if b.Len() == 0 { - return b.String() - } - - return b.String()[0 : b.Len()-2] + return builder.PostgreSqlJoin(elems) } diff --git a/tools/goctl/model/sql/builderx/builder_test.go b/tools/goctl/model/sql/builderx/builder_test.go deleted file mode 100644 index 5f3f46148e65..000000000000 --- a/tools/goctl/model/sql/builderx/builder_test.go +++ /dev/null @@ -1,125 +0,0 @@ -package builderx - -import ( - "fmt" - "testing" - - "github.com/go-xorm/builder" - "github.com/stretchr/testify/assert" -) - -type mockedUser struct { - // 自增id - ID string `db:"id" json:"id,omitempty"` - // 姓名 - UserName string `db:"user_name" json:"userName,omitempty"` - // 1男,2女 - Sex int `db:"sex" json:"sex,omitempty"` - UUID string `db:"uuid" uuid:"uuid,omitempty"` - Age int `db:"age" json:"age"` -} - -var ( - userFieldsWithRawStringQuote = RawFieldNames(mockedUser{}) - userFieldsWithoutRawStringQuote = FieldNames(mockedUser{}) -) - -func TestFieldNames(t *testing.T) { - t.Run("old", func(t *testing.T) { - var u mockedUser - out := FieldNames(&u) - expected := []string{"id", "user_name", "sex", "uuid", "age"} - assert.Equal(t, expected, out) - }) - - t.Run("new", func(t *testing.T) { - var u mockedUser - out := RawFieldNames(&u) - expected := []string{"`id`", "`user_name`", "`sex`", "`uuid`", "`age`"} - assert.Equal(t, expected, out) - }) -} - -func TestNewEq(t *testing.T) { - u := &mockedUser{ - ID: "123456", - UserName: "wahaha", - } - out := NewEq(u) - fmt.Println(out) - actual := builder.Eq{"id": "123456", "user_name": "wahaha"} - assert.Equal(t, out, actual) -} - -// @see https://github.com/go-xorm/builder -func TestBuilderSql(t *testing.T) { - u := &mockedUser{ - ID: "123123", - } - fields := RawFieldNames(u) - eq := NewEq(u) - sql, args, err := builder.Select(fields...).From("user").Where(eq).ToSQL() - fmt.Println(sql, args, err) - - actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE id=?" - actualArgs := []interface{}{"123123"} - assert.Equal(t, sql, actualSQL) - assert.Equal(t, args, actualArgs) -} - -func TestBuildSqlDefaultValue(t *testing.T) { - eq := builder.Eq{} - eq["age"] = 0 - eq["user_name"] = "" - - t.Run("raw", func(t *testing.T) { - sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(eq).ToSQL() - fmt.Println(sql, args, err) - - actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE age=? AND user_name=?" - actualArgs := []interface{}{0, ""} - assert.Equal(t, sql, actualSQL) - assert.Equal(t, args, actualArgs) - }) - - t.Run("withour raw quote", func(t *testing.T) { - sql, args, err := builder.Select(userFieldsWithoutRawStringQuote...).From("user").Where(eq).ToSQL() - fmt.Println(sql, args, err) - - actualSQL := "SELECT id,user_name,sex,uuid,age FROM user WHERE age=? AND user_name=?" - actualArgs := []interface{}{0, ""} - assert.Equal(t, sql, actualSQL) - assert.Equal(t, args, actualArgs) - }) -} - -func TestBuilderSqlIn(t *testing.T) { - u := &mockedUser{ - Age: 18, - } - gtU := NewGt(u) - in := builder.In("id", []string{"1", "2", "3"}) - sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(in).And(gtU).ToSQL() - fmt.Println(sql, args, err) - - actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE id IN (?,?,?) AND age>?" - actualArgs := []interface{}{"1", "2", "3", 18} - assert.Equal(t, sql, actualSQL) - assert.Equal(t, args, actualArgs) -} - -func TestBuildSqlLike(t *testing.T) { - like := builder.Like{"name", "wang"} - sql, args, err := builder.Select(userFieldsWithRawStringQuote...).From("user").Where(like).ToSQL() - fmt.Println(sql, args, err) - - actualSQL := "SELECT `id`,`user_name`,`sex`,`uuid`,`age` FROM user WHERE name LIKE ?" - actualArgs := []interface{}{"%wang%"} - assert.Equal(t, sql, actualSQL) - assert.Equal(t, args, actualArgs) -} - -func TestJoin(t *testing.T) { - ret := PostgreSqlJoin([]string{"name", "age"}) - assert.Equal(t, "name = $2, age = $3", ret) -} diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go index bbc677e7af20..b5af6252c20b 100644 --- a/tools/goctl/model/sql/template/import.go +++ b/tools/goctl/model/sql/template/import.go @@ -12,7 +12,7 @@ var ( "github.com/tal-tech/go-zero/core/stores/sqlc" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stringx" - "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" + "github.com/tal-tech/go-zero/core/stores/builderx" ) ` // ImportsNoCache defines a import template for model in normal case @@ -25,7 +25,7 @@ var ( "github.com/tal-tech/go-zero/core/stores/sqlc" "github.com/tal-tech/go-zero/core/stores/sqlx" "github.com/tal-tech/go-zero/core/stringx" - "github.com/tal-tech/go-zero/tools/goctl/model/sql/builderx" + "github.com/tal-tech/go-zero/core/stores/builderx" ) ` ) diff --git a/tools/goctl/model/sql/template/insert.go b/tools/goctl/model/sql/template/insert.go index 67757f861d8e..62a48c499562 100644 --- a/tools/goctl/model/sql/template/insert.go +++ b/tools/goctl/model/sql/template/insert.go @@ -2,7 +2,7 @@ package template // Insert defines a template for insert code in model var Insert = ` -func (m *default{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) { +func (m *default{{.upperStartCamelObject}}Model) Insert(data *{{.upperStartCamelObject}}) (sql.Result,error) { {{if .withCache}}{{if .containsIndexCache}}{{.keys}} ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet) @@ -15,5 +15,5 @@ func (m *default{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelO } ` -// InsertMethod defines a interface method template for insert code in model -var InsertMethod = `Insert(data {{.upperStartCamelObject}}) (sql.Result,error)` +// InsertMethod defines an interface method template for insert code in model +var InsertMethod = `Insert(data *{{.upperStartCamelObject}}) (sql.Result,error)` diff --git a/tools/goctl/model/sql/template/update.go b/tools/goctl/model/sql/template/update.go index 04fdfe1e1022..b4792d56b5a9 100644 --- a/tools/goctl/model/sql/template/update.go +++ b/tools/goctl/model/sql/template/update.go @@ -2,7 +2,7 @@ package template // Update defines a template for generating update codes var Update = ` -func (m *default{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error { +func (m *default{{.upperStartCamelObject}}Model) Update(data *{{.upperStartCamelObject}}) error { {{if .withCache}}{{.keys}} _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder) @@ -14,4 +14,4 @@ func (m *default{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelO ` // UpdateMethod defines an interface method template for generating update codes -var UpdateMethod = `Update(data {{.upperStartCamelObject}}) error` +var UpdateMethod = `Update(data *{{.upperStartCamelObject}}) error`