diff --git a/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_issue_test.go b/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_issue_test.go index 1ed9853e5d5..02d5f5936da 100644 --- a/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_issue_test.go +++ b/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_issue_test.go @@ -66,6 +66,7 @@ func Test_Gen_Dao_Issue2572(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: nil, FieldMapping: nil, } @@ -155,6 +156,7 @@ func Test_Gen_Dao_Issue2616(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: nil, FieldMapping: nil, } @@ -266,6 +268,7 @@ func Test_Gen_Dao_Issue2746(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: nil, FieldMapping: nil, } @@ -338,6 +341,7 @@ func Test_Gen_Dao_Issue3459(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: nil, } ) diff --git a/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_test.go b/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_test.go index bbe18735dbb..863c1090e4f 100644 --- a/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_test.go +++ b/cmd/gf/internal/cmd/cmd_z_unit_gen_dao_test.go @@ -69,6 +69,7 @@ func Test_Gen_Dao_Default(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: nil, FieldMapping: nil, } @@ -161,6 +162,7 @@ func Test_Gen_Dao_TypeMapping(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{ "int": { Type: "int64", @@ -263,6 +265,7 @@ func Test_Gen_Dao_FieldMapping(t *testing.T) { NoJsonTag: false, NoModelComment: false, Clear: false, + GenTable: false, TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{ "int": { Type: "int64", diff --git a/cmd/gf/internal/cmd/gendao.zip b/cmd/gf/internal/cmd/gendao.zip new file mode 100644 index 00000000000..46dace4f024 Binary files /dev/null and b/cmd/gf/internal/cmd/gendao.zip differ diff --git a/cmd/gf/internal/cmd/gendao/gendao.go b/cmd/gf/internal/cmd/gendao/gendao.go index 684c10bc30b..54204755e0e 100644 --- a/cmd/gf/internal/cmd/gendao/gendao.go +++ b/cmd/gf/internal/cmd/gendao/gendao.go @@ -47,8 +47,10 @@ type ( JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"` ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"` DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"` + TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"` DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"` EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"` + TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"` TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"` TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"` TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"` @@ -61,6 +63,7 @@ type ( NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"` NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"` Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"` + GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"` TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"` FieldMapping map[DBTableFieldName]CustomAttributeType `name:"fieldMapping" short:"fm" brief:"{CGenDaoBriefFieldMapping}" orphan:"true"` @@ -279,6 +282,14 @@ func doGenDaoForArray(ctx context.Context, index int, in CGenDaoInput) { NewTableNames: newTableNames, ShardingTableSet: shardingNewTableSet, }) + // Table: table fields. + generateTable(ctx, CGenDaoInternalInput{ + CGenDaoInput: in, + DB: db, + TableNames: tableNames, + NewTableNames: newTableNames, + ShardingTableSet: shardingNewTableSet, + }) // Do. generateDo(ctx, CGenDaoInternalInput{ CGenDaoInput: in, diff --git a/cmd/gf/internal/cmd/gendao/gendao_table.go b/cmd/gf/internal/cmd/gendao/gendao_table.go new file mode 100644 index 00000000000..4af71fc1ce4 --- /dev/null +++ b/cmd/gf/internal/cmd/gendao/gendao_table.go @@ -0,0 +1,147 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gendao + +import ( + "bytes" + "context" + "path/filepath" + "sort" + "strconv" + "strings" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gconv" + + "github.com/gogf/gf/cmd/gf/v2/internal/consts" + "github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog" + "github.com/gogf/gf/cmd/gf/v2/internal/utility/utils" +) + +// generateTable generates dao files for given tables. +func generateTable(ctx context.Context, in CGenDaoInternalInput) { + dirPathTable := gfile.Join(in.Path, in.TablePath) + if !in.GenTable { + if gfile.Exists(dirPathTable) { + in.genItems.AppendDirPath(dirPathTable) + } + return + } + in.genItems.AppendDirPath(dirPathTable) + for i := 0; i < len(in.TableNames); i++ { + var ( + realTableName = in.TableNames[i] + newTableName = in.NewTableNames[i] + ) + generateTableSingle(ctx, generateTableSingleInput{ + CGenDaoInternalInput: in, + TableName: realTableName, + NewTableName: newTableName, + DirPathTable: dirPathTable, + }) + } +} + +// generateTableSingleInput is the input parameter for generateTableSingle. +type generateTableSingleInput struct { + CGenDaoInternalInput + // TableName specifies the table name of the table. + TableName string + // NewTableName specifies the prefix-stripped or custom edited name of the table. + NewTableName string + DirPathTable string +} + +// generateTableSingle generates dao files for a single table. +func generateTableSingle(ctx context.Context, in generateTableSingleInput) { + // Generating table data preparing. + fieldMap, err := in.DB.TableFields(ctx, in.TableName) + if err != nil { + mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err) + } + + tableNameSnakeCase := gstr.CaseSnake(in.NewTableName) + fileName := gstr.Trim(tableNameSnakeCase, "-_.") + if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" { + // Add suffix to avoid the table name which contains "_test", + // which would make the go file a testing file. + fileName += "_table" + } + path := filepath.FromSlash(gfile.Join(in.DirPathTable, fileName+".go")) + in.genItems.AppendGeneratedFilePath(path) + if in.OverwriteDao || !gfile.Exists(path) { + var ( + ctx = context.Background() + tplContent = getTemplateFromPathOrDefault( + in.TplDaoTablePath, consts.TemplateGenTableContent, + ) + ) + tplView.ClearAssigns() + tplView.Assigns(gview.Params{ + tplVarGroupName: in.Group, + tplVarTableName: in.TableName, + tplVarTableNameCamelCase: formatFieldName(in.NewTableName, FieldNameCaseCamel), + tplVarPackageName: filepath.Base(in.TablePath), + tplVarTableFields: generateTableFields(fieldMap), + }) + indexContent, err := tplView.ParseContent(ctx, tplContent) + if err != nil { + mlog.Fatalf("parsing template content failed: %v", err) + } + if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil { + mlog.Fatalf("writing content to '%s' failed: %v", path, err) + } else { + utils.GoFmt(path) + mlog.Print("generated:", gfile.RealPath(path)) + } + } +} + +// generateTableFields generates and returns the field definition content for specified table. +func generateTableFields(fields map[string]*gdb.TableField) string { + var buf bytes.Buffer + fieldNames := make([]string, 0, len(fields)) + for fieldName := range fields { + fieldNames = append(fieldNames, fieldName) + } + sort.Slice(fieldNames, func(i, j int) bool { + return fields[fieldNames[i]].Index < fields[fieldNames[j]].Index // asc + }) + for index, fieldName := range fieldNames { + field := fields[fieldName] + buf.WriteString(" " + strconv.Quote(field.Name) + ": {\n") + buf.WriteString(" Index: " + gconv.String(field.Index) + ",\n") + buf.WriteString(" Name: " + strconv.Quote(field.Name) + ",\n") + buf.WriteString(" Type: " + strconv.Quote(field.Type) + ",\n") + buf.WriteString(" Null: " + gconv.String(field.Null) + ",\n") + buf.WriteString(" Key: " + strconv.Quote(field.Key) + ",\n") + buf.WriteString(" Default: " + generateDefaultValue(field.Default) + ",\n") + buf.WriteString(" Extra: " + strconv.Quote(field.Extra) + ",\n") + buf.WriteString(" Comment: " + strconv.Quote(field.Comment) + ",\n") + buf.WriteString(" },") + if index != len(fieldNames)-1 { + buf.WriteString("\n") + } + } + return buf.String() +} + +// generateDefaultValue generates and returns the default value definition for specified field. +func generateDefaultValue(value interface{}) string { + if value == nil { + return "nil" + } + switch v := value.(type) { + case string: + return strconv.Quote(v) + default: + return gconv.String(v) + } +} diff --git a/cmd/gf/internal/cmd/gendao/gendao_tag.go b/cmd/gf/internal/cmd/gendao/gendao_tag.go index 6feeb80817f..25bc84258f3 100644 --- a/cmd/gf/internal/cmd/gendao/gendao_tag.go +++ b/cmd/gf/internal/cmd/gendao/gendao_tag.go @@ -60,6 +60,7 @@ CONFIGURATION SUPPORT CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables` CGenDaoBriefImportPrefix = `custom import prefix for generated go files` CGenDaoBriefDaoPath = `directory path for storing generated dao files under path` + CGenDaoBriefTablePath = `directory path for storing generated table files under path` CGenDaoBriefDoPath = `directory path for storing generated do files under path` CGenDaoBriefEntityPath = `directory path for storing generated entity files under path` CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder` @@ -69,6 +70,7 @@ CONFIGURATION SUPPORT CGenDaoBriefNoJsonTag = `no json tag will be added for each field` CGenDaoBriefNoModelComment = `no model comment will be added for each field` CGenDaoBriefClear = `delete all generated go files that do not exist in database` + CGenDaoBriefGenTable = `generate table files` CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table` CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table` CGenDaoBriefShardingPattern = `sharding pattern for table name, e.g. "users_?" will be replace tables "users_001,users_002,..." to "users" dao` @@ -98,6 +100,7 @@ generated json tag case for model struct, cases are as follows: tplVarTableNameCamelLowerCase = `TplTableNameCamelLowerCase` tplVarTableSharding = `TplTableSharding` tplVarTableShardingPrefix = `TplTableShardingPrefix` + tplVarTableFields = `TplTableFields` tplVarPackageImports = `TplPackageImports` tplVarImportPrefix = `TplImportPrefix` tplVarStructDefine = `TplStructDefine` @@ -126,6 +129,7 @@ func init() { `CGenDaoBriefStdTime`: CGenDaoBriefStdTime, `CGenDaoBriefWithTime`: CGenDaoBriefWithTime, `CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath, + `CGenDaoBriefTablePath`: CGenDaoBriefTablePath, `CGenDaoBriefDoPath`: CGenDaoBriefDoPath, `CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath, `CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport, @@ -137,6 +141,7 @@ func init() { `CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag, `CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment, `CGenDaoBriefClear`: CGenDaoBriefClear, + `CGenDaoBriefGenTable`: CGenDaoBriefGenTable, `CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping, `CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping, `CGenDaoBriefShardingPattern`: CGenDaoBriefShardingPattern, diff --git a/cmd/gf/internal/consts/consts_gen_dao_template_table.go b/cmd/gf/internal/consts/consts_gen_dao_template_table.go new file mode 100644 index 00000000000..a60c92d5def --- /dev/null +++ b/cmd/gf/internal/consts/consts_gen_dao_template_table.go @@ -0,0 +1,35 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package consts + +const TemplateGenTableContent = ` +// ================================================================================= +// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. +// ================================================================================= + +package {{.TplPackageName}} + +import ( + "context" + + "github.com/gogf/gf/v2/database/gdb" +) + +// {{.TplTableNameCamelCase}} defines the fields of table "{{.TplTableName}}" with their properties. +// This map is used internally by GoFrame ORM to understand table structure. +var {{.TplTableNameCamelCase}} = map[string]*gdb.TableField{ +{{.TplTableFields}} +} + +// Set{{.TplTableNameCamelCase}}TableFields registers the table fields definition to the database instance. +// db: database instance that implements gdb.DB interface. +// schema: optional schema/namespace name, especially for databases that support schemas. +func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db gdb.DB, schema ...string) error { + return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...) +} + +` diff --git a/database/gdb/gdb_core.go b/database/gdb/gdb_core.go index c335f7f20b0..d34c58ed37d 100644 --- a/database/gdb/gdb_core.go +++ b/database/gdb/gdb_core.go @@ -757,6 +757,30 @@ func (c *Core) GetInnerMemCache() *gcache.Cache { return c.innerMemCache } +func (c *Core) SetTableFields(ctx context.Context, table string, fields map[string]*TableField, schema ...string) error { + if table == "" { + return gerror.NewCode(gcode.CodeInvalidParameter, "table name cannot be empty") + } + charL, charR := c.db.GetChars() + table = gstr.Trim(table, charL+charR) + if gstr.Contains(table, " ") { + return gerror.NewCode( + gcode.CodeInvalidParameter, + "function TableFields supports only single table operations", + ) + } + var ( + innerMemCache = c.GetInnerMemCache() + // prefix:group@schema#table + cacheKey = genTableFieldsCacheKey( + c.db.GetGroup(), + gutil.GetOrDefaultStr(c.db.GetSchema(), schema...), + table, + ) + ) + return innerMemCache.Set(ctx, cacheKey, fields, gcache.DurationNoExpire) +} + // GetTablesWithCache retrieves and returns the table names of current database with cache. func (c *Core) GetTablesWithCache() ([]string, error) { var (