Skip to content

Commit 1682cc9

Browse files
authored
feat(gdb): Allow to set table field metadata and allow to generate table fields registration code when generating dao (gogf#4460)
`gdb`在第一次查询时会拉取一次`table`的`fields meta`信息,为后续orm的字段过滤和时间特性服务,`gen dao`时已经获得了`table`的所有`fields meta`,提供一个生成`table`的功能,允许客户自行决定是否生成,是否注入已知表结构缓存到指定`db`实例。 1. 能解决部分兼容`mysql`的二开数据库在获取`fields meta`时无法和`mysql`保持一致的问题。 2. 可以模拟表字段信息而不需要真实的数据库连接,对于已知的表结构,可以直接设置缓存,在无法连接数据库的情况下,仍然可以使用表字段信息,使用gdb构建sql时不需要受限于实际数据库即可使用`gdb.ToSQL()`方法。 4. 提升访问速度 生成的示例目录 <img width="389" height="670" alt="SCR-20251010-ntne" src="https://github.com/user-attachments/assets/ebb08e70-cce1-4b73-9128-6ff784e4df3b" /> 生成的示例代码 ```golang // ================================================================================= // This file is auto-generated by the GoFrame CLI tool. You may modify it as needed. // ================================================================================= package table import ( "context" "github.com/gogf/gf/v2/database/gdb" ) // RolePermissions defines the fields of table "role_permissions" with their properties. // This map is used internally by GoFrame ORM to understand table structure. var RolePermissions = map[string]*gdb.TableField{ "role_id": { Index: 0, Name: "role_id", Type: "bigint unsigned", Null: false, Key: "PRI", Default: nil, Extra: "", Comment: "", }, "permission_id": { Index: 1, Name: "permission_id", Type: "bigint unsigned", Null: false, Key: "PRI", Default: nil, Extra: "", Comment: "", }, "created_at": { Index: 2, Name: "created_at", Type: "timestamp", Null: false, Key: "", Default: "CURRENT_TIMESTAMP", Extra: "DEFAULT_GENERATED", Comment: "", }, "updated_at": { Index: 3, Name: "updated_at", Type: "timestamp", Null: false, Key: "", Default: "CURRENT_TIMESTAMP", Extra: "DEFAULT_GENERATED on update CURRENT_TIMESTAMP", Comment: "", }, "deleted_at": { Index: 4, Name: "deleted_at", Type: "timestamp", Null: true, Key: "", Default: nil, Extra: "", Comment: "", }, } // SetRolePermissionsTableFields 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 SetRolePermissionsTableFields(ctx context.Context, db gdb.DB, schema ...string) error { return db.GetCore().SetTableFields(ctx, "role_permissions", RolePermissions, schema...) } ```
1 parent 2742c98 commit 1682cc9

8 files changed

Lines changed: 229 additions & 0 deletions

File tree

cmd/gf/internal/cmd/cmd_z_unit_gen_dao_issue_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func Test_Gen_Dao_Issue2572(t *testing.T) {
6666
NoJsonTag: false,
6767
NoModelComment: false,
6868
Clear: false,
69+
GenTable: false,
6970
TypeMapping: nil,
7071
FieldMapping: nil,
7172
}
@@ -155,6 +156,7 @@ func Test_Gen_Dao_Issue2616(t *testing.T) {
155156
NoJsonTag: false,
156157
NoModelComment: false,
157158
Clear: false,
159+
GenTable: false,
158160
TypeMapping: nil,
159161
FieldMapping: nil,
160162
}
@@ -266,6 +268,7 @@ func Test_Gen_Dao_Issue2746(t *testing.T) {
266268
NoJsonTag: false,
267269
NoModelComment: false,
268270
Clear: false,
271+
GenTable: false,
269272
TypeMapping: nil,
270273
FieldMapping: nil,
271274
}
@@ -338,6 +341,7 @@ func Test_Gen_Dao_Issue3459(t *testing.T) {
338341
NoJsonTag: false,
339342
NoModelComment: false,
340343
Clear: false,
344+
GenTable: false,
341345
TypeMapping: nil,
342346
}
343347
)

cmd/gf/internal/cmd/cmd_z_unit_gen_dao_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func Test_Gen_Dao_Default(t *testing.T) {
6969
NoJsonTag: false,
7070
NoModelComment: false,
7171
Clear: false,
72+
GenTable: false,
7273
TypeMapping: nil,
7374
FieldMapping: nil,
7475
}
@@ -161,6 +162,7 @@ func Test_Gen_Dao_TypeMapping(t *testing.T) {
161162
NoJsonTag: false,
162163
NoModelComment: false,
163164
Clear: false,
165+
GenTable: false,
164166
TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{
165167
"int": {
166168
Type: "int64",
@@ -263,6 +265,7 @@ func Test_Gen_Dao_FieldMapping(t *testing.T) {
263265
NoJsonTag: false,
264266
NoModelComment: false,
265267
Clear: false,
268+
GenTable: false,
266269
TypeMapping: map[gendao.DBFieldTypeName]gendao.CustomAttributeType{
267270
"int": {
268271
Type: "int64",

cmd/gf/internal/cmd/gendao.zip

17.7 KB
Binary file not shown.

cmd/gf/internal/cmd/gendao/gendao.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ type (
4747
JsonCase string `name:"jsonCase" short:"j" brief:"{CGenDaoBriefJsonCase}" d:"CamelLower"`
4848
ImportPrefix string `name:"importPrefix" short:"i" brief:"{CGenDaoBriefImportPrefix}"`
4949
DaoPath string `name:"daoPath" short:"d" brief:"{CGenDaoBriefDaoPath}" d:"dao"`
50+
TablePath string `name:"tablePath" short:"tp" brief:"{CGenDaoBriefTablePath}" d:"table"`
5051
DoPath string `name:"doPath" short:"o" brief:"{CGenDaoBriefDoPath}" d:"model/do"`
5152
EntityPath string `name:"entityPath" short:"e" brief:"{CGenDaoBriefEntityPath}" d:"model/entity"`
53+
TplDaoTablePath string `name:"tplDaoTablePath" short:"t0" brief:"{CGenDaoBriefTplDaoTablePath}"`
5254
TplDaoIndexPath string `name:"tplDaoIndexPath" short:"t1" brief:"{CGenDaoBriefTplDaoIndexPath}"`
5355
TplDaoInternalPath string `name:"tplDaoInternalPath" short:"t2" brief:"{CGenDaoBriefTplDaoInternalPath}"`
5456
TplDaoDoPath string `name:"tplDaoDoPath" short:"t3" brief:"{CGenDaoBriefTplDaoDoPathPath}"`
@@ -61,6 +63,7 @@ type (
6163
NoJsonTag bool `name:"noJsonTag" short:"k" brief:"{CGenDaoBriefNoJsonTag}" orphan:"true"`
6264
NoModelComment bool `name:"noModelComment" short:"m" brief:"{CGenDaoBriefNoModelComment}" orphan:"true"`
6365
Clear bool `name:"clear" short:"a" brief:"{CGenDaoBriefClear}" orphan:"true"`
66+
GenTable bool `name:"genTable" short:"gt" brief:"{CGenDaoBriefGenTable}" orphan:"true"`
6467

6568
TypeMapping map[DBFieldTypeName]CustomAttributeType `name:"typeMapping" short:"y" brief:"{CGenDaoBriefTypeMapping}" orphan:"true"`
6669
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) {
279282
NewTableNames: newTableNames,
280283
ShardingTableSet: shardingNewTableSet,
281284
})
285+
// Table: table fields.
286+
generateTable(ctx, CGenDaoInternalInput{
287+
CGenDaoInput: in,
288+
DB: db,
289+
TableNames: tableNames,
290+
NewTableNames: newTableNames,
291+
ShardingTableSet: shardingNewTableSet,
292+
})
282293
// Do.
283294
generateDo(ctx, CGenDaoInternalInput{
284295
CGenDaoInput: in,
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package gendao
8+
9+
import (
10+
"bytes"
11+
"context"
12+
"path/filepath"
13+
"sort"
14+
"strconv"
15+
"strings"
16+
17+
"github.com/gogf/gf/v2/database/gdb"
18+
"github.com/gogf/gf/v2/os/gfile"
19+
"github.com/gogf/gf/v2/os/gview"
20+
"github.com/gogf/gf/v2/text/gstr"
21+
"github.com/gogf/gf/v2/util/gconv"
22+
23+
"github.com/gogf/gf/cmd/gf/v2/internal/consts"
24+
"github.com/gogf/gf/cmd/gf/v2/internal/utility/mlog"
25+
"github.com/gogf/gf/cmd/gf/v2/internal/utility/utils"
26+
)
27+
28+
// generateTable generates dao files for given tables.
29+
func generateTable(ctx context.Context, in CGenDaoInternalInput) {
30+
dirPathTable := gfile.Join(in.Path, in.TablePath)
31+
if !in.GenTable {
32+
if gfile.Exists(dirPathTable) {
33+
in.genItems.AppendDirPath(dirPathTable)
34+
}
35+
return
36+
}
37+
in.genItems.AppendDirPath(dirPathTable)
38+
for i := 0; i < len(in.TableNames); i++ {
39+
var (
40+
realTableName = in.TableNames[i]
41+
newTableName = in.NewTableNames[i]
42+
)
43+
generateTableSingle(ctx, generateTableSingleInput{
44+
CGenDaoInternalInput: in,
45+
TableName: realTableName,
46+
NewTableName: newTableName,
47+
DirPathTable: dirPathTable,
48+
})
49+
}
50+
}
51+
52+
// generateTableSingleInput is the input parameter for generateTableSingle.
53+
type generateTableSingleInput struct {
54+
CGenDaoInternalInput
55+
// TableName specifies the table name of the table.
56+
TableName string
57+
// NewTableName specifies the prefix-stripped or custom edited name of the table.
58+
NewTableName string
59+
DirPathTable string
60+
}
61+
62+
// generateTableSingle generates dao files for a single table.
63+
func generateTableSingle(ctx context.Context, in generateTableSingleInput) {
64+
// Generating table data preparing.
65+
fieldMap, err := in.DB.TableFields(ctx, in.TableName)
66+
if err != nil {
67+
mlog.Fatalf(`fetching tables fields failed for table "%s": %+v`, in.TableName, err)
68+
}
69+
70+
tableNameSnakeCase := gstr.CaseSnake(in.NewTableName)
71+
fileName := gstr.Trim(tableNameSnakeCase, "-_.")
72+
if len(fileName) > 5 && fileName[len(fileName)-5:] == "_test" {
73+
// Add suffix to avoid the table name which contains "_test",
74+
// which would make the go file a testing file.
75+
fileName += "_table"
76+
}
77+
path := filepath.FromSlash(gfile.Join(in.DirPathTable, fileName+".go"))
78+
in.genItems.AppendGeneratedFilePath(path)
79+
if in.OverwriteDao || !gfile.Exists(path) {
80+
var (
81+
ctx = context.Background()
82+
tplContent = getTemplateFromPathOrDefault(
83+
in.TplDaoTablePath, consts.TemplateGenTableContent,
84+
)
85+
)
86+
tplView.ClearAssigns()
87+
tplView.Assigns(gview.Params{
88+
tplVarGroupName: in.Group,
89+
tplVarTableName: in.TableName,
90+
tplVarTableNameCamelCase: formatFieldName(in.NewTableName, FieldNameCaseCamel),
91+
tplVarPackageName: filepath.Base(in.TablePath),
92+
tplVarTableFields: generateTableFields(fieldMap),
93+
})
94+
indexContent, err := tplView.ParseContent(ctx, tplContent)
95+
if err != nil {
96+
mlog.Fatalf("parsing template content failed: %v", err)
97+
}
98+
if err = gfile.PutContents(path, strings.TrimSpace(indexContent)); err != nil {
99+
mlog.Fatalf("writing content to '%s' failed: %v", path, err)
100+
} else {
101+
utils.GoFmt(path)
102+
mlog.Print("generated:", gfile.RealPath(path))
103+
}
104+
}
105+
}
106+
107+
// generateTableFields generates and returns the field definition content for specified table.
108+
func generateTableFields(fields map[string]*gdb.TableField) string {
109+
var buf bytes.Buffer
110+
fieldNames := make([]string, 0, len(fields))
111+
for fieldName := range fields {
112+
fieldNames = append(fieldNames, fieldName)
113+
}
114+
sort.Slice(fieldNames, func(i, j int) bool {
115+
return fields[fieldNames[i]].Index < fields[fieldNames[j]].Index // asc
116+
})
117+
for index, fieldName := range fieldNames {
118+
field := fields[fieldName]
119+
buf.WriteString(" " + strconv.Quote(field.Name) + ": {\n")
120+
buf.WriteString(" Index: " + gconv.String(field.Index) + ",\n")
121+
buf.WriteString(" Name: " + strconv.Quote(field.Name) + ",\n")
122+
buf.WriteString(" Type: " + strconv.Quote(field.Type) + ",\n")
123+
buf.WriteString(" Null: " + gconv.String(field.Null) + ",\n")
124+
buf.WriteString(" Key: " + strconv.Quote(field.Key) + ",\n")
125+
buf.WriteString(" Default: " + generateDefaultValue(field.Default) + ",\n")
126+
buf.WriteString(" Extra: " + strconv.Quote(field.Extra) + ",\n")
127+
buf.WriteString(" Comment: " + strconv.Quote(field.Comment) + ",\n")
128+
buf.WriteString(" },")
129+
if index != len(fieldNames)-1 {
130+
buf.WriteString("\n")
131+
}
132+
}
133+
return buf.String()
134+
}
135+
136+
// generateDefaultValue generates and returns the default value definition for specified field.
137+
func generateDefaultValue(value interface{}) string {
138+
if value == nil {
139+
return "nil"
140+
}
141+
switch v := value.(type) {
142+
case string:
143+
return strconv.Quote(v)
144+
default:
145+
return gconv.String(v)
146+
}
147+
}

cmd/gf/internal/cmd/gendao/gendao_tag.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ CONFIGURATION SUPPORT
6060
CGenDaoBriefGJsonSupport = `use gJsonSupport to use *gjson.Json instead of string for generated json fields of tables`
6161
CGenDaoBriefImportPrefix = `custom import prefix for generated go files`
6262
CGenDaoBriefDaoPath = `directory path for storing generated dao files under path`
63+
CGenDaoBriefTablePath = `directory path for storing generated table files under path`
6364
CGenDaoBriefDoPath = `directory path for storing generated do files under path`
6465
CGenDaoBriefEntityPath = `directory path for storing generated entity files under path`
6566
CGenDaoBriefOverwriteDao = `overwrite all dao files both inside/outside internal folder`
@@ -69,6 +70,7 @@ CONFIGURATION SUPPORT
6970
CGenDaoBriefNoJsonTag = `no json tag will be added for each field`
7071
CGenDaoBriefNoModelComment = `no model comment will be added for each field`
7172
CGenDaoBriefClear = `delete all generated go files that do not exist in database`
73+
CGenDaoBriefGenTable = `generate table files`
7274
CGenDaoBriefTypeMapping = `custom local type mapping for generated struct attributes relevant to fields of table`
7375
CGenDaoBriefFieldMapping = `custom local type mapping for generated struct attributes relevant to specific fields of table`
7476
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:
98100
tplVarTableNameCamelLowerCase = `TplTableNameCamelLowerCase`
99101
tplVarTableSharding = `TplTableSharding`
100102
tplVarTableShardingPrefix = `TplTableShardingPrefix`
103+
tplVarTableFields = `TplTableFields`
101104
tplVarPackageImports = `TplPackageImports`
102105
tplVarImportPrefix = `TplImportPrefix`
103106
tplVarStructDefine = `TplStructDefine`
@@ -126,6 +129,7 @@ func init() {
126129
`CGenDaoBriefStdTime`: CGenDaoBriefStdTime,
127130
`CGenDaoBriefWithTime`: CGenDaoBriefWithTime,
128131
`CGenDaoBriefDaoPath`: CGenDaoBriefDaoPath,
132+
`CGenDaoBriefTablePath`: CGenDaoBriefTablePath,
129133
`CGenDaoBriefDoPath`: CGenDaoBriefDoPath,
130134
`CGenDaoBriefEntityPath`: CGenDaoBriefEntityPath,
131135
`CGenDaoBriefGJsonSupport`: CGenDaoBriefGJsonSupport,
@@ -137,6 +141,7 @@ func init() {
137141
`CGenDaoBriefNoJsonTag`: CGenDaoBriefNoJsonTag,
138142
`CGenDaoBriefNoModelComment`: CGenDaoBriefNoModelComment,
139143
`CGenDaoBriefClear`: CGenDaoBriefClear,
144+
`CGenDaoBriefGenTable`: CGenDaoBriefGenTable,
140145
`CGenDaoBriefTypeMapping`: CGenDaoBriefTypeMapping,
141146
`CGenDaoBriefFieldMapping`: CGenDaoBriefFieldMapping,
142147
`CGenDaoBriefShardingPattern`: CGenDaoBriefShardingPattern,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved.
2+
//
3+
// This Source Code Form is subject to the terms of the MIT License.
4+
// If a copy of the MIT was not distributed with this file,
5+
// You can obtain one at https://github.com/gogf/gf.
6+
7+
package consts
8+
9+
const TemplateGenTableContent = `
10+
// =================================================================================
11+
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
12+
// =================================================================================
13+
14+
package {{.TplPackageName}}
15+
16+
import (
17+
"context"
18+
19+
"github.com/gogf/gf/v2/database/gdb"
20+
)
21+
22+
// {{.TplTableNameCamelCase}} defines the fields of table "{{.TplTableName}}" with their properties.
23+
// This map is used internally by GoFrame ORM to understand table structure.
24+
var {{.TplTableNameCamelCase}} = map[string]*gdb.TableField{
25+
{{.TplTableFields}}
26+
}
27+
28+
// Set{{.TplTableNameCamelCase}}TableFields registers the table fields definition to the database instance.
29+
// db: database instance that implements gdb.DB interface.
30+
// schema: optional schema/namespace name, especially for databases that support schemas.
31+
func Set{{.TplTableNameCamelCase}}TableFields(ctx context.Context, db gdb.DB, schema ...string) error {
32+
return db.GetCore().SetTableFields(ctx, "{{.TplTableName}}", {{.TplTableNameCamelCase}}, schema...)
33+
}
34+
35+
`

database/gdb/gdb_core.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,30 @@ func (c *Core) GetInnerMemCache() *gcache.Cache {
757757
return c.innerMemCache
758758
}
759759

760+
func (c *Core) SetTableFields(ctx context.Context, table string, fields map[string]*TableField, schema ...string) error {
761+
if table == "" {
762+
return gerror.NewCode(gcode.CodeInvalidParameter, "table name cannot be empty")
763+
}
764+
charL, charR := c.db.GetChars()
765+
table = gstr.Trim(table, charL+charR)
766+
if gstr.Contains(table, " ") {
767+
return gerror.NewCode(
768+
gcode.CodeInvalidParameter,
769+
"function TableFields supports only single table operations",
770+
)
771+
}
772+
var (
773+
innerMemCache = c.GetInnerMemCache()
774+
// prefix:group@schema#table
775+
cacheKey = genTableFieldsCacheKey(
776+
c.db.GetGroup(),
777+
gutil.GetOrDefaultStr(c.db.GetSchema(), schema...),
778+
table,
779+
)
780+
)
781+
return innerMemCache.Set(ctx, cacheKey, fields, gcache.DurationNoExpire)
782+
}
783+
760784
// GetTablesWithCache retrieves and returns the table names of current database with cache.
761785
func (c *Core) GetTablesWithCache() ([]string, error) {
762786
var (

0 commit comments

Comments
 (0)