![ff2fe1836a195982c59d60bb44ac7edb.png](https://img-blog.csdnimg.cn/img_convert/ff2fe1836a195982c59d60bb44ac7edb.png)
缘起
最近在工作学习的过程中,接触到一些通过自动生成代码的方式来减少重复的工作量,以及自动生成文档或桩代码的方式,包括:
- 使用Kubernetes CRD,可以通过code-generator工具自动生成客户端代码以及一些其他工具函数
- 使用goa,可以通过定义的DSL自动生成服务端的框架代码,以及文档等
一般有以下几种方式来自动生成代码:
- 通过 goast 获取代码的抽象语法树,然后通过抽象语法树来生成对应的代码
- 通过自定义DSL的方式获取代码信息,然后生成代码
- 通过反射获取信息来生成代码
正好最近在学习使用golang的ast解析工具,遂通过实现一个简单的工具来加深理解。该工具将自动读取xorm的类型信息,并自动生成对应的建表sql语句。
目标
首先明确该工具的适用目标及范围:
- 只支持xorm框架
- 只支持少量的xorm框架特性: 包括created, updated, pk, unique, notnull
- 只支持少量的sql类型: 包括BIGINT, INT, VARCHAR, DATETIME
- 支持设定表名: 通过 +genTable spec
- 只支持mysql
测试数据如下:
package main
import "time"
// User is orm for user
// +genTable: user
type User struct {
Id int64
Name string `xorm:"unique notnull"`
Salt string
Age int
Passwd string `xorm:"varchar(200)"`
Created time.Time `xorm:"created"`
Update time.Time `xorm:"updated"`
}
/*
+genTable:
*/
// User2 and User3
type (
// User2
// +genTable: user2
User2 struct {
// user2 line
Uid int `xorm:"pk 'uid'"`
}
// comment group
// User3
// +genTable:
User3 struct {
// user3 line
}
)
type I1 interface {
}
最终输出的建表语句如下:
CREATE TABLE `user` (
`Id` BIGINT,
`Name` VARCHAR(255) NOT NULL,
`Salt` VARCHAR(255),
`Age` INT,
`Passwd` varchar(200),
`Created` DATETIME DEFAULT CURRENT_TIMESTAMP,
`Update` DATETIME ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(`Id`),
UNIQUE KEY `Name` (`Name`)
);
CREATE TABLE `user2` (
`uid` INT,
PRIMARY KEY(`uid`)
);
实现
1. 生成 ast
ast 即抽象语法树, go/parser 包提供了工具来解析生成ast:
fs := token.NewFileSet()
f, err := parser.ParseFile(fs, file, src, parser.ParseComments)
其中:
- 通过token.NewFileSet来生成一个FileSet对象,这个对象会保存更详细的源码位置信息
- file是文件名, 当src为nil时会读取该文件的内容;当src是字节数组/字符串时会直接将src的内容作为文件内容解析
- 需要通过ParseComments参数告知parser解析注释,因为我们的 +genTable 是通过注释实现的
2. 获取所有的table struct
在得到 ast 之后,我们需要筛选出对应的表的struct,首先定义一个结构体保存具体的信息:
type tableStruct struct {
node *ast.StructType
tableName string
}
其中表名可能来自以下内容:
- 在多个类型上的 +genTable spec,这时不能跟表名,表示使用类型名作为表名,当有多个类型都直接使用类型名作为表名时可使用
- 在单个类型上的 +genTable spec,这时可以指定表