Skip to content

Commit

Permalink
feat: support diy TableName (go-gorm#687)
Browse files Browse the repository at this point in the history
* feat: support diy TableName

* feat: fmt
  • Loading branch information
qqxhb authored Oct 21, 2022
1 parent 3c1f921 commit 4fb174e
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 133 deletions.
11 changes: 11 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ type Config struct {
dataTypeMap map[string]func(detailType string) (dataType string)
fieldJSONTagNS func(columnName string) (tagContent string)
fieldNewTagNS func(columnName string) (tagContent string)

modelOpts []ModelOpt
}

// WithOpts set global model options
func (cfg *Config) WithOpts(opts ...ModelOpt) {
if cfg.modelOpts == nil {
cfg.modelOpts = opts
} else {
cfg.modelOpts = append(cfg.modelOpts, opts...)
}
}

// WithDbNameOpts set get database name function
Expand Down
14 changes: 14 additions & 0 deletions field_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,3 +251,17 @@ var (
return func() []interface{} { return methods }
}
)

var (
DefaultMethodTableWithNamer = (&defaultModel{}).TableName
)

type defaultModel struct {
}

func (*defaultModel) TableName(namer schema.Namer) string {
if namer == nil {
return "@@table"
}
return namer.TableName("@@table")
}
5 changes: 5 additions & 0 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ func (g *Generator) GenerateModelFrom(obj helper.Object) *generate.QueryStructMe
}

func (g *Generator) genModelConfig(tableName string, modelName string, modelOpts []ModelOpt) *model.Config {
if modelOpts == nil {
modelOpts = g.modelOpts
} else {
modelOpts = append(modelOpts, g.modelOpts...)
}
return &model.Config{
ModelPkg: g.Config.ModelPkgPath,
TablePrefix: g.getTablePrefix(),
Expand Down
14 changes: 13 additions & 1 deletion internal/generate/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,30 @@ func (b *QueryStructMeta) StructComment() string {
// ReviseDIYMethod check diy method duplication name
func (b *QueryStructMeta) ReviseDIYMethod() error {
var duplicateMethodName []string
var tableName *parser.Method
methods := make([]*parser.Method, 0, len(b.ModelMethods))
methodMap := make(map[string]bool, len(b.ModelMethods))
for _, method := range b.ModelMethods {
if methodMap[method.MethodName] || method.MethodName == "TableName" {
if methodMap[method.MethodName] {
duplicateMethodName = append(duplicateMethodName, method.MethodName)
continue
}
if method.MethodName == "TableName" {
tableName = method
}
method.Receiver.Package = ""
method.Receiver.Type = b.ModelStructName
methods = append(methods, method)
methodMap[method.MethodName] = true
}
if tableName == nil {
methods = append(methods, parser.DefaultMethodTableName(b.ModelStructName))
} else {
//e.g. return "@@table" => return TableNameUser
tableName.Body = strings.ReplaceAll(tableName.Body, "\"@@table\"", "TableName"+b.ModelStructName)
//e.g. return "t_@@table" => return "t_user"
tableName.Body = strings.ReplaceAll(tableName.Body, "@@table", b.TableName)
}
b.ModelMethods = methods

if len(duplicateMethodName) > 0 {
Expand Down
145 changes: 145 additions & 0 deletions internal/parser/method.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package parser

import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"strings"
)

func DefaultMethodTableName(structName string) *Method {
return &Method{
Receiver: Param{IsPointer: true, Type: structName},
MethodName: "TableName",
Doc: fmt.Sprint("TableName ", structName, "'s table name "),
Result: []Param{{Type: "string"}},
Body: fmt.Sprintf("{\n\treturn TableName%s\n} ", structName),
}
}

// Method Apply to query struct and base struct custom method
type Method struct {
Receiver Param
MethodName string
Doc string
Params []Param
Result []Param
Body string
}

// FuncSign function signature
func (m Method) FuncSign() string {
return fmt.Sprintf("%s(%s) (%s)", m.MethodName, m.GetParamInTmpl(), m.GetResultParamInTmpl())
}

// GetBaseStructTmpl return method bind info string
func (m *Method) GetBaseStructTmpl() string {
return m.Receiver.TmplString()
}

// GetParamInTmpl return param list
func (m *Method) GetParamInTmpl() string {
return paramToString(m.Params)
}

// GetResultParamInTmpl return result list
func (m *Method) GetResultParamInTmpl() string {
return paramToString(m.Result)
}

// paramToString param list to string used in tmpl
func paramToString(params []Param) string {
res := make([]string, len(params))
for i, param := range params {
res[i] = param.TmplString()
}
return strings.Join(res, ",")
}

// DocComment return comment sql add "//" every line
func (m *Method) DocComment() string {
return strings.Replace(strings.TrimSpace(m.Doc), "\n", "\n//", -1)
}

// DIYMethods user Custom methods bind to db base struct
type DIYMethods struct {
BaseStructType string
MethodName string
pkgPath string
currentFile string
pkgFiles []string
Methods []*Method
}

func (m *DIYMethods) parserPath(path string) error {
pathList := strings.Split(path, ".")
if len(pathList) < 3 {
return fmt.Errorf("parser diy method error")
}

m.pkgPath = strings.Join(pathList[:len(pathList)-2], ".")
methodName := pathList[len(pathList)-1]
m.MethodName = methodName[:len(methodName)-3]

structName := pathList[len(pathList)-2]
m.BaseStructType = strings.Trim(structName, "()*")
return nil
}

// Visit ast visit function
func (m *DIYMethods) Visit(n ast.Node) (w ast.Visitor) {
switch t := n.(type) {
case *ast.FuncDecl:
// check base struct and method name is expect
structMeta := getParamList(t.Recv)
if len(structMeta) != 1 {
return
}
if structMeta[0].Type != m.BaseStructType {
return
}
// if m.MethodName is null will generate all methods
if m.MethodName != "" && m.MethodName != t.Name.Name {
return
}

// use ast read bind start package is UNDEFINED ,set it null string
structMeta[0].Package = ""
m.Methods = append(m.Methods, &Method{
Receiver: structMeta[0],
MethodName: t.Name.String(),
Doc: t.Doc.Text(),
Body: getBody(m.currentFile, int(t.Body.Pos()), int(t.Body.End())),
Params: getParamList(t.Type.Params),
Result: getParamList(t.Type.Results),
})
}

return m
}

// read old file get method body
func getBody(fileName string, start, end int) string {
f1, err := ioutil.ReadFile(fileName)
if err != nil {
return "{}"
}

return string(f1[start-1 : end-1])
}

// LoadMethods ast read file get diy method
func (m *DIYMethods) LoadMethods() error {
for _, filename := range m.pkgFiles {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("can't parse file %q: %s", filename, err)
}
m.currentFile = filename
ast.Walk(m, f)
}

return nil
}
126 changes: 0 additions & 126 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"go/ast"
"go/parser"
"go/token"
"io/ioutil"
"log"
"path"
"path/filepath"
Expand Down Expand Up @@ -304,128 +303,3 @@ func astGetType(expr ast.Expr) string {
}
return ""
}

// Method Apply to query struct and base struct custom method
type Method struct {
Receiver Param
MethodName string
Doc string
Params []Param
Result []Param
Body string
}

// FuncSign function signature
func (m Method) FuncSign() string {
return fmt.Sprintf("%s(%s) (%s)", m.MethodName, m.GetParamInTmpl(), m.GetResultParamInTmpl())
}

// GetBaseStructTmpl return method bind info string
func (m *Method) GetBaseStructTmpl() string {
return m.Receiver.TmplString()
}

// GetParamInTmpl return param list
func (m *Method) GetParamInTmpl() string {
return paramToString(m.Params)
}

// GetResultParamInTmpl return result list
func (m *Method) GetResultParamInTmpl() string {
return paramToString(m.Result)
}

// paramToString param list to string used in tmpl
func paramToString(params []Param) string {
res := make([]string, len(params))
for i, param := range params {
res[i] = param.TmplString()
}
return strings.Join(res, ",")
}

// DocComment return comment sql add "//" every line
func (m *Method) DocComment() string {
return strings.Replace(strings.TrimSpace(m.Doc), "\n", "\n//", -1)
}

// DIYMethods user Custom methods bind to db base struct
type DIYMethods struct {
BaseStructType string
MethodName string
pkgPath string
currentFile string
pkgFiles []string
Methods []*Method
}

func (m *DIYMethods) parserPath(path string) error {
pathList := strings.Split(path, ".")
if len(pathList) < 3 {
return fmt.Errorf("parser diy method error")
}

m.pkgPath = strings.Join(pathList[:len(pathList)-2], ".")
methodName := pathList[len(pathList)-1]
m.MethodName = methodName[:len(methodName)-3]

structName := pathList[len(pathList)-2]
m.BaseStructType = strings.Trim(structName, "()*")
return nil
}

// Visit ast visit function
func (m *DIYMethods) Visit(n ast.Node) (w ast.Visitor) {
switch t := n.(type) {
case *ast.FuncDecl:
// check base struct and method name is expect
structMeta := getParamList(t.Recv)
if len(structMeta) != 1 {
return
}
if structMeta[0].Type != m.BaseStructType {
return
}
// if m.MethodName is null will generate all methods
if m.MethodName != "" && m.MethodName != t.Name.Name {
return
}

// use ast read bind start package is UNDEFINED ,set it null string
structMeta[0].Package = ""
m.Methods = append(m.Methods, &Method{
Receiver: structMeta[0],
MethodName: t.Name.String(),
Doc: t.Doc.Text(),
Body: getBody(m.currentFile, int(t.Body.Pos()), int(t.Body.End())),
Params: getParamList(t.Type.Params),
Result: getParamList(t.Type.Results),
})
}

return m
}

// read old file get method body
func getBody(fileName string, start, end int) string {
f1, err := ioutil.ReadFile(fileName)
if err != nil {
return "{}"
}

return string(f1[start-1 : end-1])
}

// LoadMethods ast read file get diy method
func (m *DIYMethods) LoadMethods() error {
for _, filename := range m.pkgFiles {
f, err := parser.ParseFile(token.NewFileSet(), filename, nil, parser.ParseComments)
if err != nil {
return fmt.Errorf("can't parse file %q: %s", filename, err)
}
m.currentFile = filename
ast.Walk(m, f)
}

return nil
}
7 changes: 1 addition & 6 deletions internal/template/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"gorm.io/datatypes"
"gorm.io/gorm"
"gorm.io/gorm/schema"
{{range .ImportPkgPaths}}{{.}} ` + "\n" + `{{end}}
)
Expand All @@ -28,12 +29,6 @@ type {{.ModelStructName}} struct {
`{{end}}
}
{{if .TableName -}}
// TableName {{.ModelStructName}}'s table name
func (*{{.ModelStructName}}) TableName() string {
return TableName{{.ModelStructName}}
}
{{- end}}
`

// ModelMethod model struct DIY method
Expand Down

0 comments on commit 4fb174e

Please sign in to comment.