1. 背景
对于后台开发新的需求时,一般会先进行各种表的设计,写各个表的建表语句
然后根据建立的表,写对应的model代码、基础的增删改查代码(基础的增删改查服务可以划入DAO(Data Access Object)层)。
model代码都有一些固定的格式,可以通过解析SQL建表语句,来自动生成model代码,
对于不同的表,基础的增删改查代码大概率只是换了个表名或者数据库,因此也可以自动生成。
通过自动生成代码,减少重复工作,提示开发效率。
2. 整体介绍
目录结构如下,具体代码建Github sql2code
.
├── README.md
├── code2file
│ └── code2file.go
├── go.mod
├── go.sum
├── main.go
├── sql2code_tpl
│ ├── sql2code_tpl.go
│ ├── sql2dao_tpl.txt
│ └── sql2model_tpl.txt
├── sql2dao
│ ├── sql2dao.go
│ └── sql2dao_test.go
├── sql2model
│ ├── sql2model.go
│ ├── sql2model_test.go
│ └── tidb_types.go
├── test
│ └── t_student.sql
└── util
└── util_strings
└── util_strings.go
7 directories, 15 files
sql2code_tpl: 主要是model和dao的模版代码
sql2model:MySQL建表语句到Model代码的主要处理流程
sql2dao:MySQL建表语句到Dao代码到主要处理流程
有误go get TiDB的types文件夹会出现各种冲突的错误,因此只拷贝的需要的部分代码到sql2model/tidb_types.go
3. sql到model
3.1 基本思路
使用SQL解析器获得表名及每一列信息,使用模版生成model代码。
SQL解析器使用的是TiDB parser
3.2 model模版
package {{.PackageName}}
// {{.Comment}}
type {{.ModelName}} struct {
{{- range .Rows}}
{{.Name}} {{.GoType}} {{.Tags}} // {{.Comment}}
{{- end}}
}
func ({{.ModelName}}) TableName() string {
return "{{.OriginTblName}}"
}
3.3 部分实现代码
使用SQL解析器解析建表语句,获得表名,及每一列的列名,注释等信息,主要代码如下
func SQLParse(sql, tablePrefix string) (*ModelTable, error) {
cts, err := parseCreateTableStmt(sql)
if err != nil {
log.Printf("parseCreateTableStmt fail,err:%v", err)
return nil, err
}
mt := &ModelTable{}
primaryKey := ""
// table name
tblName := TableNamePrefixCut(cts.Table.Name.L, tablePrefix)
mt.ModelName = TableName2ModelName(tblName)
mt.OriginTblName = cts.Table.Name.L
// primary
for _, ctt := range cts.Constraints {
// only contain one primary key
if ctt.Tp == ast.ConstraintPrimaryKey {
if len(ctt.Keys) >= 0 {
primaryKey = ctt.Keys[0].Column.Name.L
}
break
}
}
// comment
for _, op := range cts.Options {
if op.Tp == ast.TableOptionComment {
mt.Comment = op.StrValue
}
}
modelRows := make([]ModelRow, 0, len(cts.Cols))
for _, col := range cts.Cols {
nameLow := col.Name.Name.L
modelRow := ModelRow{
Name: util_strings.ToCamel(col.Name.Name.L), // 需要去除下划线转驼峰
}
//fmt.Printf("col: %+v %+v %v %v\n", col.Name, col.Tp, HasUnsignedFlag(col.Tp.GetFlag()), col.Tp.GetType())
modelRow.GoType = sqlType2GoType(col.Tp.GetType(), col.Tp.GetFlag())
for _, op := range col.Options {
if op.Tp == ast.ColumnOptionComment {
exprVal, ok := op.Expr.(*test_driver.ValueExpr)
if !ok {
fmt.Println("op.Expr.(*test_driver.ValueExpr) fail.")
continue
}
modelRow.Comment = exprVal.Datum.GetString()
break
}
}
if primaryKey == col.Name.Name.L {
modelRow.Tags = fmt.Sprintf("`gorm:\"column:%v; primary_key\" json:\"%v\"`", nameLow, nameLow)
} else {
modelRow.Tags = fmt.Sprintf("`gorm:\"column:%v;\" json:\"%v\"`", nameLow, nameLow)
}
//fmt.Println(modelRow.Tags)
modelRows = append(modelRows, modelRow)
}
mt.Rows = modelRows
return mt, nil
}