From c51934592499745ef1f68670458e2705269b92f3 Mon Sep 17 00:00:00 2001 From: anqiansong Date: Wed, 12 Aug 2020 14:09:49 +0800 Subject: [PATCH] reactor sql generation --- go.mod | 1 + go.sum | 4 + tools/goctl/model/sql/builderx/builder.go | 97 ++++++++++++ .../goctl/model/sql/builderx/builder_test.go | 101 ++++++++++++ tools/goctl/model/sql/gen/delete.go | 6 +- tools/goctl/model/sql/gen/field.go | 15 +- tools/goctl/model/sql/gen/fineonebyfield.go | 4 +- tools/goctl/model/sql/gen/gen.go | 5 +- tools/goctl/model/sql/gen/imports.go | 15 +- tools/goctl/model/sql/gen/keys.go | 8 +- tools/goctl/model/sql/parser/parser.go | 51 ++++-- tools/goctl/model/sql/template/field.go | 2 +- tools/goctl/model/sql/template/find.go | 12 +- tools/goctl/model/sql/template/import.go | 6 +- tools/goctl/model/sql/template/vars.go | 2 +- .../sql/test/modelwithcache/testmodel.go | 149 ++++++++++++++++++ tools/goctl/model/sql/test/sql_test.go | 20 +++ tools/goctl/model/sql/test/test.sql | 30 ++++ tools/goctl/util/stringx/string.go | 2 +- 19 files changed, 497 insertions(+), 33 deletions(-) create mode 100644 tools/goctl/model/sql/builderx/builder.go create mode 100644 tools/goctl/model/sql/builderx/builder_test.go create mode 100644 tools/goctl/model/sql/test/modelwithcache/testmodel.go create mode 100644 tools/goctl/model/sql/test/sql_test.go create mode 100644 tools/goctl/model/sql/test/test.sql diff --git a/go.mod b/go.mod index 0f57f9da1..9efa9b75a 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8 github.com/go-redis/redis v6.15.7+incompatible github.com/go-sql-driver/mysql v1.5.0 + github.com/go-xorm/builder v0.3.4 github.com/gogo/protobuf v1.3.1 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/mock v1.4.3 diff --git a/go.sum b/go.sum index 1501ac069..db9db2dc5 100644 --- a/go.sum +++ b/go.sum @@ -76,6 +76,10 @@ github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gG github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +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.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= diff --git a/tools/goctl/model/sql/builderx/builder.go b/tools/goctl/model/sql/builderx/builder.go new file mode 100644 index 000000000..ad80ba655 --- /dev/null +++ b/tools/goctl/model/sql/builderx/builder.go @@ -0,0 +1,97 @@ +package builderx + +import ( + "fmt" + "reflect" + + "github.com/go-xorm/builder" +) + +const dbTag = "db" + +func NewEq(in interface{}) builder.Eq { + return builder.Eq(ToMap(in)) +} + +func NewGt(in interface{}) builder.Gt { + return builder.Gt(ToMap(in)) +} + +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 +} + +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 +} +func FieldNamesAlias(in interface{}, alias string) []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) + tagName := "" + if tagv := fi.Tag.Get(dbTag); tagv != "" { + tagName = tagv + } else { + tagName = fi.Name + } + if len(alias) > 0 { + tagName = alias + "." + tagName + } + out = append(out, tagName) + } + return out +} diff --git a/tools/goctl/model/sql/builderx/builder_test.go b/tools/goctl/model/sql/builderx/builder_test.go new file mode 100644 index 000000000..28d1db015 --- /dev/null +++ b/tools/goctl/model/sql/builderx/builder_test.go @@ -0,0 +1,101 @@ +package builderx + +import ( + "fmt" + "testing" + + "github.com/go-xorm/builder" + "github.com/stretchr/testify/assert" +) + +type ( + User 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 userFields = FieldNames(User{}) + +func TestFieldNames(t *testing.T) { + var u User + out := FieldNames(&u) + fmt.Println(out) + actual := []string{"id", "user_name", "sex", "uuid", "age"} + assert.Equal(t, out, actual) +} + +func TestNewEq(t *testing.T) { + u := &User{ + 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 := &User{ + Id: "123123", + } + fields := FieldNames(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) { + var eq = builder.Eq{} + eq["age"] = 0 + eq["user_name"] = "" + + sql, args, err := builder.Select(userFields...).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 := &User{ + Age: 18, + } + gtU := NewGt(u) + in := builder.In("id", []string{"1", "2", "3"}) + sql, args, err := builder.Select(userFields...).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(userFields...).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) +} diff --git a/tools/goctl/model/sql/gen/delete.go b/tools/goctl/model/sql/gen/delete.go index 872a17945..84f60ef02 100644 --- a/tools/goctl/model/sql/gen/delete.go +++ b/tools/goctl/model/sql/gen/delete.go @@ -13,7 +13,9 @@ func genDelete(table Table, withCache bool) (string, error) { keySet := collection.NewSet() keyVariableSet := collection.NewSet() for fieldName, key := range table.CacheKey { - keySet.AddStr(key.KeyExpression) + if fieldName == table.PrimaryKey.Name.Source() { + keySet.AddStr(key.KeyExpression) + } if fieldName != table.PrimaryKey.Name.Source() { keySet.AddStr(key.DataKeyExpression) } @@ -33,7 +35,7 @@ func genDelete(table Table, withCache bool) (string, error) { "upperStartCamelObject": camel, "withCache": withCache, "containsIndexCache": containsIndexCache, - "lowerStartCamelPrimaryKey": stringx.From(camel).LowerStart(), + "lowerStartCamelPrimaryKey": stringx.From(table.PrimaryKey.Name.Snake2Camel()).LowerStart(), "dataType": table.PrimaryKey.DataType, "keys": strings.Join(keySet.KeysStr(), "\n"), "originalPrimaryKey": table.PrimaryKey.Name.Source(), diff --git a/tools/goctl/model/sql/gen/field.go b/tools/goctl/model/sql/gen/field.go index d49882221..125800db6 100644 --- a/tools/goctl/model/sql/gen/field.go +++ b/tools/goctl/model/sql/gen/field.go @@ -21,13 +21,18 @@ func genFields(fields []parser.Field) (string, error) { } func genField(field parser.Field) (string, error) { + tag, err := genTag(field.Name.Source()) + if err != nil { + return "", err + } output, err := templatex.With("types"). Parse(template.Field). - Execute(map[string]string{ - "name": field.Name.Snake2Camel(), - "type": field.DataType, - "tag": field.Name.Source(), - "comment": field.Comment, + Execute(map[string]interface{}{ + "name": field.Name.Snake2Camel(), + "type": field.DataType, + "tag": tag, + "hasComment": field.Comment != "", + "comment": field.Comment, }) if err != nil { return "", err diff --git a/tools/goctl/model/sql/gen/fineonebyfield.go b/tools/goctl/model/sql/gen/fineonebyfield.go index 06f547874..62c66000d 100644 --- a/tools/goctl/model/sql/gen/fineonebyfield.go +++ b/tools/goctl/model/sql/gen/fineonebyfield.go @@ -14,7 +14,7 @@ func genFineOneByField(table Table, withCache bool) (string, error) { var list []string camelTableName := table.Name.Snake2Camel() for _, field := range table.Fields { - if field.IsPrimaryKey { + if field.IsPrimaryKey || !field.IsKey { continue } camelFieldName := field.Name.Snake2Camel() @@ -25,7 +25,7 @@ func genFineOneByField(table Table, withCache bool) (string, error) { "withCache": withCache, "cacheKey": table.CacheKey[field.Name.Source()].KeyExpression, "cacheKeyVariable": table.CacheKey[field.Name.Source()].Variable, - "primaryKeyLeft": table.CacheKey[table.Name.Source()].Left, + "primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left, "lowerStartCamelObject": stringx.From(camelTableName).LowerStart(), "lowerStartCamelField": stringx.From(camelFieldName).LowerStart(), "upperStartCamelPrimaryKey": table.PrimaryKey.Name.Snake2Camel(), diff --git a/tools/goctl/model/sql/gen/gen.go b/tools/goctl/model/sql/gen/gen.go index cfca15cd4..c25631e77 100644 --- a/tools/goctl/model/sql/gen/gen.go +++ b/tools/goctl/model/sql/gen/gen.go @@ -104,7 +104,10 @@ func (g *defaultGenerator) genModel(in parser.Table, withCache bool) (string, er if err != nil { return "", err } - importsCode := genImports() + importsCode, err := genImports(withCache) + if err != nil { + return "", err + } var table Table table.Table = in table.CacheKey = m diff --git a/tools/goctl/model/sql/gen/imports.go b/tools/goctl/model/sql/gen/imports.go index 85c253644..52be2cf37 100644 --- a/tools/goctl/model/sql/gen/imports.go +++ b/tools/goctl/model/sql/gen/imports.go @@ -1,9 +1,18 @@ package gen import ( - sqltemplate "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/template" + "github.com/tal-tech/go-zero/tools/goctl/util/templatex" ) -func genImports() string { - return sqltemplate.Imports +func genImports(withCache bool) (string, error) { + output, err := templatex.With("import"). + Parse(template.Imports). + Execute(map[string]interface{}{ + "withCache": withCache, + }) + if err != nil { + return "", err + } + return output.String(), nil } diff --git a/tools/goctl/model/sql/gen/keys.go b/tools/goctl/model/sql/gen/keys.go index 5d9437b9f..bc5ba855d 100644 --- a/tools/goctl/model/sql/gen/keys.go +++ b/tools/goctl/model/sql/gen/keys.go @@ -34,7 +34,7 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) { } camelFieldName := field.Name.Snake2Camel() lowerStartCamelFieldName := stringx.From(camelFieldName).LowerStart() - left := fmt.Sprintf("cache%s%sPrefix", camelTableName, camelFieldName) + left := fmt.Sprintf("cache%s%sPrefix", lowerStartCamelTableName, camelFieldName) right := fmt.Sprintf("cache#%s#%s#", lowerStartCamelTableName, lowerStartCamelFieldName) variable := fmt.Sprintf("%s%sKey", lowerStartCamelTableName, camelFieldName) m[field.Name.Source()] = Key{ @@ -42,9 +42,9 @@ func genCacheKeys(table parser.Table) (map[string]Key, error) { Left: left, Right: right, Variable: variable, - KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", %s)`, variable, "%s", lowerStartCamelFieldName), - DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", data.%s)`, variable, "%s", lowerStartCamelFieldName), - RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#user#id#%s", resp.%s)`, variable, "%s", lowerStartCamelFieldName), + KeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", %s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", lowerStartCamelFieldName), + DataKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", data.%s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", camelFieldName), + RespKeyExpression: fmt.Sprintf(`%s := fmt.Sprintf("cache#%s#%s#%s", resp.%s)`, variable, lowerStartCamelTableName, lowerStartCamelFieldName, "%v", camelFieldName), } } return m, nil diff --git a/tools/goctl/model/sql/parser/parser.go b/tools/goctl/model/sql/parser/parser.go index dec0da9cb..b3701ae3b 100644 --- a/tools/goctl/model/sql/parser/parser.go +++ b/tools/goctl/model/sql/parser/parser.go @@ -8,6 +8,14 @@ import ( "github.com/xwb1989/sqlparser" ) +const ( + none = iota + primary + unique + normal + spatial +) + type ( Table struct { Name stringx.String @@ -26,6 +34,7 @@ type ( IsPrimaryKey bool Comment string } + KeyType int ) func Parse(ddl string) (*Table, error) { @@ -50,6 +59,7 @@ func Parse(ddl string) (*Table, error) { columns := tableSpec.Columns indexes := tableSpec.Indexes + keyMap := make(map[string]KeyType) for _, index := range indexes { info := index.Info if info == nil { @@ -59,13 +69,33 @@ func Parse(ddl string) (*Table, error) { if len(index.Columns) > 1 { return nil, errPrimaryKey } - break + keyMap[index.Columns[0].Column.String()] = primary + continue + } + // can optimize + if len(index.Columns) > 1 { + continue + } + column := index.Columns[0] + columnName := column.Column.String() + camelColumnName := stringx.From(columnName).Snake2Camel() + // by default, createTime|updateTime findOne is not used. + if camelColumnName == "CreateTime" || camelColumnName == "UpdateTime" { + continue + } + if info.Unique { + keyMap[columnName] = unique + } else if info.Spatial { + keyMap[columnName] = spatial + } else { + keyMap[columnName] = normal } } var ( fields []Field primaryKey Primary ) + for _, column := range columns { if column == nil { continue @@ -83,17 +113,18 @@ func Parse(ddl string) (*Table, error) { field.DataBaseType = column.Type.Type field.DataType = dataType field.Comment = comment - // see line 1194 https://github.com/xwb1989/sqlparser/blob/master/ast.go - field.IsKey = column.Type.KeyOpt != 0 - field.IsPrimaryKey = column.Type.KeyOpt == 1 - fields = append(fields, field) - // see line 1195 https://github.com/xwb1989/sqlparser/blob/master/ast.go - if column.Type.KeyOpt == 1 { - primaryKey.Field = field - if column.Type.Autoincrement { - primaryKey.AutoIncrement = true + key, ok := keyMap[column.Name.String()] + if ok { + field.IsKey = true + field.IsPrimaryKey = key == primary + if field.IsPrimaryKey { + primaryKey.Field = field + if column.Type.Autoincrement { + primaryKey.AutoIncrement = true + } } } + fields = append(fields, field) } return &Table{ Name: stringx.From(tableName), diff --git a/tools/goctl/model/sql/template/field.go b/tools/goctl/model/sql/template/field.go index ec0b02843..493091ccd 100644 --- a/tools/goctl/model/sql/template/field.go +++ b/tools/goctl/model/sql/template/field.go @@ -1,3 +1,3 @@ package template -var Field = `{{.name}} {{.type}} {{.tag}} {{.comment}}` +var Field = `{{.name}} {{.type}} {{.tag}} {{if .hasComment}}// {{.comment}}{{end}}` diff --git a/tools/goctl/model/sql/template/find.go b/tools/goctl/model/sql/template/find.go index 696e2d9d1..e67692055 100644 --- a/tools/goctl/model/sql/template/find.go +++ b/tools/goctl/model/sql/template/find.go @@ -55,5 +55,15 @@ func (m *{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{ default: return nil, err } -} +}{{else}}var resp {{.upperStartCamelObject}} + query := ` + "`" + `select ` + "`" + ` + {{.lowerStartCamelObject}}Rows + ` + "`" + ` from ` + "` + " + `m.table ` + " + `" + ` where {{.originalField}} limit 1` + "`" + ` + err := m.QueryRowNoCache(&resp, query, {{.lowerStartCamelField}}) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + }{{end}} ` diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go index 41f7103cc..96491ad05 100644 --- a/tools/goctl/model/sql/template/import.go +++ b/tools/goctl/model/sql/template/import.go @@ -2,13 +2,15 @@ package template var Imports = ` import ( - "database/sql" - "strings" + "database/sql"{{if .withCache}} + "fmt" + {{end}}"strings" "time" "github.com/tal-tech/go-zero/core/stores/cache" "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" ) ` diff --git a/tools/goctl/model/sql/template/vars.go b/tools/goctl/model/sql/template/vars.go index e883013cb..e2bfc9960 100644 --- a/tools/goctl/model/sql/template/vars.go +++ b/tools/goctl/model/sql/template/vars.go @@ -4,7 +4,7 @@ var Vars = ` var ( {{.lowerStartCamelObject}}FieldNames = builderx.FieldNames(&{{.upperStartCamelObject}}{}) {{.lowerStartCamelObject}}Rows = strings.Join({{.lowerStartCamelObject}}FieldNames, ",") - {{.lowerStartCamelObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{.if autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ",") + {{.lowerStartCamelObject}}RowsExpectAutoSet = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "create_time", "update_time"), ",") {{.lowerStartCamelObject}}RowsWithPlaceHolder = strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "create_time", "update_time"), "=?,") + "=?" {{.cacheKeys}} diff --git a/tools/goctl/model/sql/test/modelwithcache/testmodel.go b/tools/goctl/model/sql/test/modelwithcache/testmodel.go new file mode 100644 index 000000000..09f34df68 --- /dev/null +++ b/tools/goctl/model/sql/test/modelwithcache/testmodel.go @@ -0,0 +1,149 @@ +package model + +import ( + "database/sql" + "fmt" + "strings" + "time" + + "github.com/tal-tech/go-zero/core/stores/cache" + "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" +) + +var ( + userCamelFieldNames = builderx.FieldNames(&UserCamel{}) + userCamelRows = strings.Join(userCamelFieldNames, ",") + userCamelRowsExpectAutoSet = strings.Join(stringx.Remove(userCamelFieldNames, "create_time", "update_time"), ",") + userCamelRowsWithPlaceHolder = strings.Join(stringx.Remove(userCamelFieldNames, "id", "create_time", "update_time"), "=?,") + "=?" + + cacheuserCamelIdPrefix = "cache#userCamel#id#" + cacheuserCamelNamePrefix = "cache#userCamel#name#" + cacheuserCamelMobilePrefix = "cache#userCamel#mobile#" + + ErrNotFound = sqlx.ErrNotFound +) + +type ( + UserCamelModel struct { + sqlc.CachedConn + table string + } + + UserCamel struct { + Id int64 `db:"id"` + Name string `db:"name"` // 用户名称 + Password string `db:"password"` // 用户密码 + Mobile string `db:"mobile"` // 手机号 + Gender string `db:"gender"` // 男|女|未公开 + Nickname string `db:"nickname"` // 用户昵称 + CreateTime time.Time `db:"createTime"` + UpdateTime time.Time `db:"updateTime"` + } +) + +func NewUserCamelModel(conn sqlx.SqlConn, c cache.CacheConf, table string) *UserCamelModel { + return &UserCamelModel{ + CachedConn: sqlc.NewConn(conn, c), + table: table, + } +} + +func (m *UserCamelModel) Insert(data UserCamel) error { + query := `insert into ` + m.table + `(` + userCamelRowsExpectAutoSet + `) value (?, ?, ?, ?, ?, ?)` + _, err := m.ExecNoCache(query, data.Id, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname) + return err +} + +func (m *UserCamelModel) FindOne(id int64) (*UserCamel, error) { + userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", id) + var resp UserCamel + err := m.QueryRow(&resp, userCamelIdKey, func(conn sqlx.SqlConn, v interface{}) error { + query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, id) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *UserCamelModel) FindOneByName(name string) (*UserCamel, error) { + userCamelNameKey := fmt.Sprintf("cache#userCamel#name#%v", name) + var resp UserCamel + err := m.QueryRowIndex(&resp, userCamelNameKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheuserCamelIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userCamelRows + ` from ` + m.table + ` where name = ? limit 1` + if err := conn.QueryRow(&resp, query, name); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *UserCamelModel) FindOneByMobile(mobile string) (*UserCamel, error) { + userCamelMobileKey := fmt.Sprintf("cache#userCamel#mobile#%v", mobile) + var resp UserCamel + err := m.QueryRowIndex(&resp, userCamelMobileKey, func(primary interface{}) string { + return fmt.Sprintf("%s%v", cacheuserCamelIdPrefix, primary) + }, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) { + query := `select ` + userCamelRows + ` from ` + m.table + ` where mobile = ? limit 1` + if err := conn.QueryRow(&resp, query, mobile); err != nil { + return nil, err + } + return resp.Id, nil + }, func(conn sqlx.SqlConn, v, primary interface{}) error { + query := `select ` + userCamelRows + ` from ` + m.table + ` where id = ? limit 1` + return conn.QueryRow(v, query, primary) + }) + switch err { + case nil: + return &resp, nil + case sqlc.ErrNotFound: + return nil, ErrNotFound + default: + return nil, err + } +} + +func (m *UserCamelModel) Update(data UserCamel) error { + userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", data.Id) + _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `update ` + m.table + ` set ` + userCamelRowsWithPlaceHolder + ` where id = ?` + return conn.Exec(query, data.Name, data.Password, data.Mobile, data.Gender, data.Nickname, data.Id) + }, userCamelIdKey) + return err +} + +func (m *UserCamelModel) Delete(id int64) error { + data, err := m.FindOne(id) + if err != nil { + return err + } + userCamelIdKey := fmt.Sprintf("cache#userCamel#id#%v", id) + userCamelNameKey := fmt.Sprintf("cache#userCamel#name#%v", data.Name) + userCamelMobileKey := fmt.Sprintf("cache#userCamel#mobile#%v", data.Mobile) + _, err = m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) { + query := `delete from ` + m.table + ` where id = ?` + return conn.Exec(query, id) + }, userCamelIdKey, userCamelNameKey, userCamelMobileKey) + return err +} diff --git a/tools/goctl/model/sql/test/sql_test.go b/tools/goctl/model/sql/test/sql_test.go new file mode 100644 index 000000000..4611003a1 --- /dev/null +++ b/tools/goctl/model/sql/test/sql_test.go @@ -0,0 +1,20 @@ +package test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tal-tech/go-zero/tools/goctl/model/sql/gen" +) + +func TestGenSqlWithCache(t *testing.T) { + generator := gen.NewDefaultGenerator("./test.sql", "./modelwithcache") + err := generator.Start(true) + assert.Nil(t, err) +} + +func TestGenSqlWithoutCache(t *testing.T) { + generator := gen.NewDefaultGenerator("./test.sql", "./modelwithoutcache") + err := generator.Start(false) + assert.Nil(t, err) +} diff --git a/tools/goctl/model/sql/test/test.sql b/tools/goctl/model/sql/test/test.sql new file mode 100644 index 000000000..9b96bfee9 --- /dev/null +++ b/tools/goctl/model/sql/test/test.sql @@ -0,0 +1,30 @@ + +-- user_snake +CREATE TABLE `user_snake` ( + `id` bigint(10) NOT NULL AUTO_INCREMENT, + `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称', + `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码', + `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号', + `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开', + `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称', + `create_time` timestamp NULL DEFAULT NULL, + `update_time` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `name_index` (`name`), + KEY `mobile_index` (`mobile`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; + +-- userCamel (disable AUTO_INCREMENT) +CREATE TABLE `userCamel` ( + `id` bigint(10) NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称', + `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码', + `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号', + `gender` char(5) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开', + `nickname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称', + `createTime` timestamp NULL DEFAULT NULL, + `updateTime` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `nameIndex` (`name`), + KEY `mobile_index` (`mobile`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; \ No newline at end of file diff --git a/tools/goctl/util/stringx/string.go b/tools/goctl/util/stringx/string.go index 88e46abb5..bf5a182b5 100644 --- a/tools/goctl/util/stringx/string.go +++ b/tools/goctl/util/stringx/string.go @@ -87,7 +87,7 @@ func (s String) LowerStart() string { if !unicode.IsUpper(r) && !unicode.IsLower(r) { return s.source } - return string(r) + s.source[1:] + return string(unicode.ToLower(r)) + s.source[1:] } // it will not ignore spaces