gozero template (#147)

* model/rpc generate code from template cache

* delete unused(deprecated) code

* support template init|update|clean|revert

* model: return the execute result for insert and update operation

* // deprecated: containsAny

* add template test

* add default buildVersion

* update build version
This commit is contained in:
Keson
2020-10-21 14:59:35 +08:00
committed by GitHub
parent fe0d0687f5
commit 41964f9d52
43 changed files with 908 additions and 119 deletions

View File

@@ -1,5 +1,9 @@
# Change log
## 2020-10-19
* 增加template
## 2020-08-20
* 新增支持通过连接数据库生成model

View File

@@ -0,0 +1,20 @@
package converter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestConvertDataType(t *testing.T) {
v, err := ConvertDataType("tinyint")
assert.Nil(t, err)
assert.Equal(t, "int64", v)
v, err = ConvertDataType("timestamp")
assert.Nil(t, err)
assert.Equal(t, "time.Time", v)
_, err = ConvertDataType("float32")
assert.NotNil(t, err)
}

View File

@@ -4,4 +4,4 @@
goctl model mysql ddl -src="./sql/user.sql" -dir="./sql/model" -c
# generate model with cache from data source
goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"
#goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="table1,table2" -dir="./model"

View File

@@ -22,8 +22,13 @@ func genDelete(table Table, withCache bool) (string, error) {
}
camel := table.Name.ToCamel()
text, err := util.LoadTemplate(category, deleteTemplateFile, template.Delete)
if err != nil {
return "", err
}
output, err := util.With("delete").
Parse(template.Delete).
Parse(text).
Execute(map[string]interface{}{
"upperStartCamelObject": camel,
"withCache": withCache,

View File

@@ -25,8 +25,14 @@ func genField(field parser.Field) (string, error) {
if err != nil {
return "", err
}
text, err := util.LoadTemplate(category, fieldTemplateFile, template.Field)
if err != nil {
return "", err
}
output, err := util.With("types").
Parse(template.Field).
Parse(text).
Execute(map[string]interface{}{
"name": field.Name.ToCamel(),
"type": field.DataType,

View File

@@ -8,8 +8,13 @@ import (
func genFindOne(table Table, withCache bool) (string, error) {
camel := table.Name.ToCamel()
text, err := util.LoadTemplate(category, findOneTemplateFile, template.FindOne)
if err != nil {
return "", err
}
output, err := util.With("findOne").
Parse(template.FindOne).
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": camel,

View File

@@ -10,7 +10,12 @@ import (
)
func genFindOneByField(table Table, withCache bool) (string, string, error) {
t := util.With("findOneByField").Parse(template.FindOneByField)
text, err := util.LoadTemplate(category, findOneByFieldTemplateFile, template.FindOneByField)
if err != nil {
return "", "", err
}
t := util.With("findOneByField").Parse(text)
var list []string
camelTableName := table.Name.ToCamel()
for _, field := range table.Fields {
@@ -33,10 +38,16 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
if err != nil {
return "", "", err
}
list = append(list, output.String())
}
if withCache {
out, err := util.With("findOneByFieldExtraMethod").Parse(template.FindOneByFieldExtraMethod).Execute(map[string]interface{}{
text, err := util.LoadTemplate(category, findOneByFieldExtraMethodTemplateFile, template.FindOneByFieldExtraMethod)
if err != nil {
return "", "", err
}
out, err := util.With("findOneByFieldExtraMethod").Parse(text).Execute(map[string]interface{}{
"upperStartCamelObject": camelTableName,
"primaryKeyLeft": table.CacheKey[table.PrimaryKey.Name.Source()].Left,
"lowerStartCamelObject": stringx.From(camelTableName).UnTitle(),
@@ -45,6 +56,7 @@ func genFindOneByField(table Table, withCache bool) (string, string, error) {
if err != nil {
return "", "", err
}
return strings.Join(list, "\n"), out.String(), nil
}
return strings.Join(list, "\n"), "", nil

View File

@@ -7,20 +7,32 @@ import (
func genImports(withCache, timeImport bool) (string, error) {
if withCache {
buffer, err := util.With("import").Parse(template.Imports).Execute(map[string]interface{}{
text, err := util.LoadTemplate(category, importsTemplateFile, template.Imports)
if err != nil {
return "", err
}
buffer, err := util.With("import").Parse(text).Execute(map[string]interface{}{
"time": timeImport,
})
if err != nil {
return "", err
}
return buffer.String(), nil
} else {
buffer, err := util.With("import").Parse(template.ImportsNoCache).Execute(map[string]interface{}{
text, err := util.LoadTemplate(category, importsWithNoCacheTemplateFile, template.ImportsNoCache)
if err != nil {
return "", err
}
buffer, err := util.With("import").Parse(text).Execute(map[string]interface{}{
"time": timeImport,
})
if err != nil {
return "", err
}
return buffer.String(), nil
}
}

View File

@@ -34,8 +34,13 @@ func genInsert(table Table, withCache bool) (string, error) {
expressionValues = append(expressionValues, "data."+camel)
}
camel := table.Name.ToCamel()
text, err := util.LoadTemplate(category, insertTemplateFile, template.Insert)
if err != nil {
return "", err
}
output, err := util.With("insert").
Parse(template.Insert).
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"containsIndexCache": table.ContainsUniqueKey,
@@ -49,5 +54,6 @@ func genInsert(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -12,7 +12,7 @@ type (
// {{prefix}}=cache
// key:id
Key struct {
VarExpression string // cacheUserIdPrefix="cache#user#id#"
VarExpression string // cacheUserIdPrefix = "cache#User#id#"
Left string // cacheUserIdPrefix
Right string // cache#user#id#
Variable string // userIdKey

View File

@@ -0,0 +1,77 @@
package gen
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/parser"
"github.com/tal-tech/go-zero/tools/goctl/util/stringx"
)
func TestGenCacheKeys(t *testing.T) {
m, err := genCacheKeys(parser.Table{
Name: stringx.From("user"),
PrimaryKey: parser.Primary{
Field: parser.Field{
Name: stringx.From("id"),
DataBaseType: "bigint",
DataType: "int64",
IsKey: false,
IsPrimaryKey: true,
IsUniqueKey: false,
Comment: "自增id",
},
AutoIncrement: true,
},
Fields: []parser.Field{
{
Name: stringx.From("mobile"),
DataBaseType: "varchar",
DataType: "string",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: true,
Comment: "手机号",
},
{
Name: stringx.From("name"),
DataBaseType: "varchar",
DataType: "string",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: true,
Comment: "姓名",
},
{
Name: stringx.From("createTime"),
DataBaseType: "timestamp",
DataType: "time.Time",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: false,
Comment: "创建时间",
},
{
Name: stringx.From("updateTime"),
DataBaseType: "timestamp",
DataType: "time.Time",
IsKey: false,
IsPrimaryKey: false,
IsUniqueKey: false,
Comment: "更新时间",
},
},
})
assert.Nil(t, err)
for fieldName, key := range m {
name := stringx.From(fieldName)
assert.Equal(t, fmt.Sprintf(`cacheUser%sPrefix = "cache#User#%s#"`, name.ToCamel(), name.UnTitle()), key.VarExpression)
assert.Equal(t, fmt.Sprintf(`cacheUser%sPrefix`, name.ToCamel()), key.Left)
assert.Equal(t, fmt.Sprintf(`cache#User#%s#`, name.UnTitle()), key.Right)
assert.Equal(t, fmt.Sprintf(`user%sKey`, name.ToCamel()), key.Variable)
assert.Equal(t, `user`+name.ToCamel()+`Key := fmt.Sprintf("%s%v", cacheUser`+name.ToCamel()+`Prefix,`+name.UnTitle()+`)`, key.KeyExpression)
}
}

View File

@@ -6,8 +6,13 @@ import (
)
func genNew(table Table, withCache bool) (string, error) {
text, err := util.LoadTemplate(category, modelNewTemplateFile, template.New)
if err != nil {
return "", err
}
output, err := util.With("new").
Parse(template.New).
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": table.Name.ToCamel(),
@@ -15,5 +20,6 @@ func genNew(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -9,8 +9,13 @@ func genTag(in string) (string, error) {
if in == "" {
return in, nil
}
text, err := util.LoadTemplate(category, tagTemplateFile, template.Tag)
if err != nil {
return "", err
}
output, err := util.With("tag").
Parse(template.Tag).
Parse(text).
Execute(map[string]interface{}{
"field": in,
})

View File

@@ -0,0 +1,72 @@
package gen
import (
"fmt"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util"
"github.com/urfave/cli"
)
const (
category = "model"
deleteTemplateFile = "delete.tpl"
fieldTemplateFile = "filed.tpl"
findOneTemplateFile = "find-one.tpl"
findOneByFieldTemplateFile = "find-one-by-field.tpl"
findOneByFieldExtraMethodTemplateFile = "find-one-by-filed-extra-method.tpl"
importsTemplateFile = "import.tpl"
importsWithNoCacheTemplateFile = "import-no-cache.tpl"
insertTemplateFile = "insert.tpl"
modelTemplateFile = "model.tpl"
modelNewTemplateFile = "model-new.tpl"
tagTemplateFile = "tag.tpl"
typesTemplateFile = "types.tpl"
updateTemplateFile = "update.tpl"
varTemplateFile = "var.tpl"
)
var templates = map[string]string{
deleteTemplateFile: template.Delete,
fieldTemplateFile: template.Field,
findOneTemplateFile: template.FindOne,
findOneByFieldTemplateFile: template.FindOneByField,
findOneByFieldExtraMethodTemplateFile: template.FindOneByFieldExtraMethod,
importsTemplateFile: template.Imports,
importsWithNoCacheTemplateFile: template.ImportsNoCache,
insertTemplateFile: template.Insert,
modelTemplateFile: template.Model,
modelNewTemplateFile: template.New,
tagTemplateFile: template.Tag,
typesTemplateFile: template.Types,
updateTemplateFile: template.Update,
varTemplateFile: template.Vars,
}
func GenTemplates(_ *cli.Context) error {
return util.InitTemplates(category, templates)
}
func RevertTemplate(name string) error {
content, ok := templates[name]
if !ok {
return fmt.Errorf("%s: no such file name", name)
}
return util.CreateTemplate(category, name, content)
}
func Clean() error {
return util.Clean(category)
}
func Update(category string) error {
err := Clean()
if err != nil {
return err
}
return util.InitTemplates(category, templates)
}
func GetCategory() string {
return category
}

View File

@@ -0,0 +1,93 @@
package gen
import (
"io/ioutil"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/tal-tech/go-zero/tools/goctl/model/sql/template"
"github.com/tal-tech/go-zero/tools/goctl/util"
)
func TestGenTemplates(t *testing.T) {
err := util.InitTemplates(category, templates)
assert.Nil(t, err)
dir, err := util.GetTemplateDir(category)
assert.Nil(t, err)
file := filepath.Join(dir, "model-new.tpl")
data, err := ioutil.ReadFile(file)
assert.Nil(t, err)
assert.Equal(t, string(data), template.New)
}
func TestRevertTemplate(t *testing.T) {
name := "model-new.tpl"
err := util.InitTemplates(category, templates)
assert.Nil(t, err)
dir, err := util.GetTemplateDir(category)
assert.Nil(t, err)
file := filepath.Join(dir, name)
data, err := ioutil.ReadFile(file)
assert.Nil(t, err)
modifyData := string(data) + "modify"
err = util.CreateTemplate(category, name, modifyData)
assert.Nil(t, err)
data, err = ioutil.ReadFile(file)
assert.Nil(t, err)
assert.Equal(t, string(data), modifyData)
assert.Nil(t, RevertTemplate(name))
data, err = ioutil.ReadFile(file)
assert.Nil(t, err)
assert.Equal(t, template.New, string(data))
}
func TestClean(t *testing.T) {
name := "model-new.tpl"
err := util.InitTemplates(category, templates)
assert.Nil(t, err)
assert.Nil(t, Clean())
dir, err := util.GetTemplateDir(category)
assert.Nil(t, err)
file := filepath.Join(dir, name)
_, err = ioutil.ReadFile(file)
assert.NotNil(t, err)
}
func TestUpdate(t *testing.T) {
name := "model-new.tpl"
err := util.InitTemplates(category, templates)
assert.Nil(t, err)
dir, err := util.GetTemplateDir(category)
assert.Nil(t, err)
file := filepath.Join(dir, name)
data, err := ioutil.ReadFile(file)
assert.Nil(t, err)
modifyData := string(data) + "modify"
err = util.CreateTemplate(category, name, modifyData)
assert.Nil(t, err)
data, err = ioutil.ReadFile(file)
assert.Nil(t, err)
assert.Equal(t, string(data), modifyData)
assert.Nil(t, Update(category))
data, err = ioutil.ReadFile(file)
assert.Nil(t, err)
assert.Equal(t, template.New, string(data))
}

View File

@@ -11,8 +11,14 @@ func genTypes(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
text, err := util.LoadTemplate(category, typesTemplateFile, template.Types)
if err != nil {
return "", err
}
output, err := util.With("types").
Parse(template.Types).
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": table.Name.ToCamel(),
@@ -21,5 +27,6 @@ func genTypes(table Table, withCache bool) (string, error) {
if err != nil {
return "", err
}
return output.String(), nil
}

View File

@@ -22,8 +22,13 @@ func genUpdate(table Table, withCache bool) (string, error) {
}
expressionValues = append(expressionValues, "data."+table.PrimaryKey.Name.ToCamel())
camelTableName := table.Name.ToCamel()
text, err := util.LoadTemplate(category, updateTemplateFile, template.Update)
if err != nil {
return "", err
}
output, err := util.With("update").
Parse(template.Update).
Parse(text).
Execute(map[string]interface{}{
"withCache": withCache,
"upperStartCamelObject": camelTableName,
@@ -36,5 +41,6 @@ func genUpdate(table Table, withCache bool) (string, error) {
if err != nil {
return "", nil
}
return output.String(), nil
}

View File

@@ -14,8 +14,13 @@ func genVars(table Table, withCache bool) (string, error) {
keys = append(keys, v.VarExpression)
}
camel := table.Name.ToCamel()
text, err := util.LoadTemplate(category, varTemplateFile, template.Vars)
if err != nil {
return "", err
}
output, err := util.With("var").
Parse(template.Vars).
Parse(text).
GoFmt(true).
Execute(map[string]interface{}{
"lowerStartCamelObject": stringx.From(camel).UnTitle(),

View File

@@ -17,11 +17,9 @@ func TestParseSelect(t *testing.T) {
}
func TestParseCreateTable(t *testing.T) {
_, err := Parse("CREATE TABLE `user_snake` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
assert.Nil(t, err)
}
func TestParseCreateTable2(t *testing.T) {
_, err := Parse("create table `user_snake` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
table, err := Parse("CREATE TABLE `user_snake` (\n `id` bigint(10) NOT NULL AUTO_INCREMENT,\n `name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名称',\n `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户密码',\n `mobile` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',\n `gender` char(5) COLLATE utf8mb4_general_ci NOT NULL COMMENT '男|女|未公开',\n `nickname` varchar(255) COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '用户昵称',\n `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,\n `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`),\n UNIQUE KEY `name_index` (`name`),\n KEY `mobile_index` (`mobile`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;")
assert.Nil(t, err)
assert.Equal(t, "user_snake", table.Name.Source())
assert.Equal(t, "id", table.PrimaryKey.Name.Source())
assert.Equal(t, true, table.ContainsTime())
}

View File

@@ -15,6 +15,7 @@ var (
)
`
ImportsNoCache = `import (
"database/sql"
"strings"
{{if .time}}"time"{{end}}

View File

@@ -1,15 +1,15 @@
package template
var Insert = `
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) error {
func (m *{{.upperStartCamelObject}}Model) Insert(data {{.upperStartCamelObject}}) (sql.Result,error) {
{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
return conn.Exec(query, {{.expressionValues}})
}, {{.keyValues}}){{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
_,err:=m.ExecNoCache(query, {{.expressionValues}})
ret,err:=m.ExecNoCache(query, {{.expressionValues}})
{{end}}{{else}}query := ` + "`" + `insert into ` + "`" + ` + m.table + ` + "` (` + " + `{{.lowerStartCamelObject}}RowsExpectAutoSet` + " + `) values ({{.expression}})` " + `
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
return err
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
return ret,err
}
`

View File

@@ -1,13 +1,13 @@
package template
var Update = `
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) error {
func (m *{{.upperStartCamelObject}}Model) Update(data {{.upperStartCamelObject}}) (sql.Result,error) {
{{if .withCache}}{{.primaryCacheKey}}
_, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
return conn.Exec(query, {{.expressionValues}})
}, {{.primaryKeyVariable}}){{else}}query := ` + "`" + `update ` + "` +" + `m.table +` + "` " + `set ` + "` +" + `{{.lowerStartCamelObject}}RowsWithPlaceHolder` + " + `" + ` where {{.originalPrimaryKey}} = ?` + "`" + `
_,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
return err
ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
return ret,err
}
`