新手问题 基于 Golang AST 自动生成建表 sql

AlexaMa · 2018年05月02日 · 16 次阅读

Originally published in liudanking.com

写后台业务的同学经常调侃自己的工作就是围绕数据表 CRUD. 虽然实际工作并不会如此简单,但是日常中的确有很多类似的重复、缺乏创造性的工作。而这种工作上是可以在一定程度上自动化的。为了提供业务研发人员开发效率,前段时间我们开发了一个后端开发工作流工具,主要提供以下功能:

  • 生成服务器 API 基础代码以及 Swagger 文档注释 (只支持 gin 框架)
  • 生成服务器 API 客户端代码
  • go struct 批量添加 tag
  • 生成 gorm model struct
  • model struct 生成 sql

因为这些功能跟我们内部的公共库有一定耦合,因此整个工具可能无法开源出来。这里,我们以model struct 生成 sql功能为例,聊聊我们在做这个工具的思路和使用到的工具。

任务

这里以我们在项目中使用的 jinzhu 同学的gorm作为 orm 库。如果你在使用 golang 的其他 orm lib,实现方式应该大同小异。

我们的任务是从下面的这个 model struct 定义:

生成 mysql 建表语句(文件):

思路

model struct 生成 sql是一个将语言A翻译为语言B的问题。而这个过程跟我们平时将源代码编译为二进制可执行程序从原理上说是没有区别的。因此,这个问题本质上是一个编译问题。一个完整的编译包含以下步骤:

对于本文要完成的任务来说,主要完成词法分析、语法分析、目标代码生成即可。

工具

要完成词法分析和语法分析,我们有上古神器 LexYacc, Yet Another Compiler-Compiler. 而我们只是想完成一个建表文件的生成任务而已,使用者两个工具有时候要自定义语法,又是要自己写 lex 和 yacc 文件,累觉不爱……

Golang 有很多其他语言羡慕不来的工具,例如 go pprof, go list, go vet 等。在语言元编程方面,go 1.4 实现了自举;而编译时候涉及到的词法分析和语法分析很早前就放在了标准库 go/ast 中。AST是 abstract syntax tree 的缩写,直译过来是抽象语法树。通过 AST,我们可以编写一个 go 程序解析 go 源代码。具体到本文要完成的任务,要编写一个这样的程序解析定义数据表的 model struct, 然后生成 sql 建表语句。

对了,印象中,我们的 beego 文档自动生成也用到了 AST.

实现

具体到我们的任务实现,可以拆分为如下几个步骤:

  • 加载源代码,生成 AST Tree
  • 获取和解析 model struct AST
  • 根据 struct field name/tag 生成 create_definition, table_options

完整代码实现,可以移步 github gorm2sql.

实现效果:

user_email.go:

type UserBase struct {
    UserId string `sql:"index:idx_ub"`
    Ip     string `sql:"unique_index:uniq_ip"`
}

type UserEmail struct {
    Id       int64    `gorm:"primary_key"`
    UserBase
    Email      string
    Sex        bool
    Age        int
    Score      float64
    UpdateTime time.Time `sql:"default:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"`
    CreateTime time.Time `sql:"default:CURRENT_TIMESTAMP"`
}
gorm2sql sql -f user_email.go -s UserEmail -o db.sql

Result:

CREATE TABLE `user_email`
(
  `id` bigint AUTO_INCREMENT NOT NULL ,
  `user_id` varchar(128) NOT NULL ,
  `ip` varchar(128) NOT NULL ,
  `email` varchar(128) NOT NULL ,
  `sex` boolean NOT NULL ,
  `age` int NOT NULL ,
  `score` double NOT NULL ,
  `update_time` datetime NOT NULL  DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `create_time` datetime NOT NULL  DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_ub (`user_id`),
  UNIQUE INDEX uniq_ip (`ip`),
  PRIMARY KEY (`id`)
) engine=innodb DEFAULT charset=utf8mb4;

扩展阅读

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册