使用antlr4 解析sql语句
最近写了一个SQL DML 转换 GraphQL 的小工具, 记录一下使用antlr4 解析 sql语句的方法
-
在这个仓库 https://github.com/antlr/grammars-v4 找到对应的语法定义文件
-
实现自定义的Visitor
-
解析 table name, colume, comment 生成GraphQL 的定义
效果如下
[main] INFO com.beyondblog.Main - sql: CREATE TABLE `Users` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL COMMENT '用户名称',
`type` tinyint DEFAULT NULL COMMENT '类型: xxxxxxxx',
`note` varchar(200) DEFAULT NULL COMMENT '备注',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '0-禁用 1-启用 2-删除',
`create_time` bigint DEFAULT NULL COMMENT '创建时间',
`update_time` bigint DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='用户'
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `id`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `name`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `type`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `note`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `status`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `create_time`
[main] INFO com.beyondblog.MySqlParserSelectVisitor - 解析字段定义 `update_time`
[main] INFO com.beyondblog.Main - (ddlStatement (createTable CREATE TABLE (tableName (fullId (uid (simpleId (engineName `Users`))))) (createDefinitions ( (createDefinition (uid (simpleId (engineName `id`))) (columnDefinition (dataType bigint unsigned) (columnConstraint (nullNotnull NOT NULL)) (columnConstraint AUTO_INCREMENT))) , (createDefinition (uid (simpleId (engineName `name`))) (columnDefinition (dataType varchar (lengthOneDimension ( (decimalLiteral 100) ))) (columnConstraint DEFAULT (defaultValue NULL)) (columnConstraint COMMENT '用户名称'))) , (createDefinition (uid (simpleId (engineName `type`))) (columnDefinition (dataType tinyint) (columnConstraint DEFAULT (defaultValue NULL)) (columnConstraint COMMENT '类型: xxxxxxxx'))) , (createDefinition (uid (simpleId (engineName `note`))) (columnDefinition (dataType varchar (lengthOneDimension ( (decimalLiteral 200) ))) (columnConstraint DEFAULT (defaultValue NULL)) (columnConstraint COMMENT '备注'))) , (createDefinition (uid (simpleId (engineName `status`))) (columnDefinition (dataType tinyint) (columnConstraint (nullNotnull NOT NULL)) (columnConstraint DEFAULT (defaultValue (constant (stringLiteral '1')))) (columnConstraint COMMENT '0-禁用 1-启用 2-删除'))) , (createDefinition (uid (simpleId (engineName `create_time`))) (columnDefinition (dataType bigint) (columnConstraint DEFAULT (defaultValue NULL)) (columnConstraint COMMENT '创建时间'))) , (createDefinition (uid (simpleId (engineName `update_time`))) (columnDefinition (dataType bigint) (columnConstraint DEFAULT (defaultValue NULL)) (columnConstraint COMMENT '修改时间'))) , (createDefinition (tableConstraint PRIMARY KEY (indexColumnNames ( (indexColumnName (uid (simpleId (engineName `id`)))) )))) )) (tableOption ENGINE = (engineName InnoDB)) (tableOption COMMENT = '用户')))
[main] INFO com.beyondblog.Main - 结果如下:
[main] INFO com.beyondblog.Main - 类型为: Users
[main] INFO com.beyondblog.Main - 字段名: id, 类型: Int, 是否必须: true
[main] INFO com.beyondblog.Main - 字段名: name, 类型: String, 是否必须: false
[main] INFO com.beyondblog.Main - 字段名: type, 类型: Int, 是否必须: false
[main] INFO com.beyondblog.Main - 字段名: note, 类型: String, 是否必须: false
[main] INFO com.beyondblog.Main - 字段名: status, 类型: Int, 是否必须: true
[main] INFO com.beyondblog.Main - 字段名: create_time, 类型: Int, 是否必须: false
[main] INFO com.beyondblog.Main - 字段名: update_time, 类型: Int, 是否必须: false
[main] INFO com.beyondblog.Main -
# 用户
type Users {
id: Int
# 用户名称
name: String
# 类型: xxxxxxxx
type: Int
# 备注
note: String
# 0-禁用 1-启用 2-删除
status: Int
# 创建时间
createTime: Int
# 修改时间
updateTime: Int
}
解析的主要逻辑
@Slf4j
public class MySqlParserSelectVisitor extends MySqlParserBaseVisitor<Void> {
private final GraphQLType graphQLType;
public MySqlParserSelectVisitor(GraphQLType graphQLType) {
this.graphQLType = graphQLType;
}
@Override
public Void visitTableOptionComment(MySqlParser.TableOptionCommentContext ctx) {
graphQLType.setDescription(getText(ctx.getChild(ctx.getChildCount() - 1)));
return visitChildren(ctx);
}
@Override
public Void visitDdlStatement(MySqlParser.DdlStatementContext ctx) {
return super.visitDdlStatement(ctx);
}
@Override
public Void visitTableName(MySqlParser.TableNameContext ctx) {
graphQLType.setTypeName(getText(ctx));
return visitChildren(ctx);
}
@Override
public Void visitColumnDeclaration(MySqlParser.ColumnDeclarationContext ctx) {
log.info("解析字段定义 {}", ctx.uid().getText());
GraphQLField graphQLField = new GraphQLField();
graphQLField.setSourceFieldName(getText(ctx.uid()));
graphQLField.setFieldName(getText(ctx.uid()));
var columnConstraint = ctx.columnDefinition().columnConstraint();
var dataType = ctx.columnDefinition().dataType();
if (dataType instanceof MySqlParser.StringDataTypeContext) {
graphQLField.setType("String");
} else if (dataType instanceof MySqlParser.DimensionDataTypeContext) {
graphQLField.setType("Int");
} else {
log.warn("未知类型");
graphQLField.setType("String");
}
for (var constraint : columnConstraint) {
if (constraint instanceof MySqlParser.CommentColumnConstraintContext) {
var comment = getText(((MySqlParser.CommentColumnConstraintContext) constraint).STRING_LITERAL());
graphQLField.setDescription(comment);
} else if (constraint instanceof MySqlParser.NullColumnConstraintContext) {
if (((MySqlParser.NullColumnConstraintContext) constraint).nullNotnull().NOT() != null) {
graphQLField.setRequired(true);
}
}
}
this.graphQLType.addField(graphQLField);
return super.visitColumnDeclaration(ctx);
}
private String getText(ParseTree ctx) {
if (ctx.getText() == null) {
return null;
}
return ctx.getText().replaceAll("`", "").replaceAll("'", "");
}
}