Antlr4之简单的sql查询解析demo

Posted 你是小KS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Antlr4之简单的sql查询解析demo相关的知识,希望对你有一定的参考价值。

当前版本:jdk1.8antlr4.8

1. 声明

当前内容主要为测试和使用Antlr4,并设计简单的SQL查询解析(本人解析IoTDB源码中发现其中使用了Antlr4来实现对sql执行的解析)

1. 首先下载antlr4.8对g4文件的编译包,官方antlr-4.8-complete.jar

2. 编写sql解析的g4文件

grammar SqlBase;

singleStatement : statement EOF ;

statement : 'SELECT' selectElements fromClause ;
    
fromClause : 'FROM' tableName ;
	
selectElements : ID ;
	
tableName : ID ;
	
ID : [a-zA-Z]+ ;

WS : [ \\r\\n\\t]+ -> skip ;

语法简述:

  1. grammar SqlBase; 表示定义解析的语法名称为SqlBase
  2. singleStatement : statement EOF ;表示我们只会使用这个语法标记解析全部文本
  3. ID : [a-zA-Z]+ ; 表示匹配的为英文字母的语法
  4. WS : [ \\r\\n\\t]+ -> skip ; 表示使用跳过空白的部分

这里其实就是解析:SELECT XXX FROM XXX的这种简单的sql语法
将上述文件使用记事本保存并修改为SqlBase.g4即可

3. 开始使用antlr生成对应的java文件

将SqlBase.g4文件放到刚才下载的complete.jar一起

然后执行命令:java -jar antlr-4.8-complete.jar SqlBase.g4

这样就得到了生成的文件:

4. 开始编写代码并解析得到执行sql的内容

对于简单的查询sql这里只需要得到查询的字段和查询的表名称即可
将上述生成的文件导入到项目中,并导入maven依赖即可

解析代码如下:

/**
 * 
 * @author hy
 * @createTime 2022-06-12 14:10:29
 * @description 测试并使用当前的antlr来对sql进行解析
 *
 */
public class SqlGrammaParseTest 
	public static void main(String[] args) 
		String sql = "SELECT name FROM users";
		CharStream stream = CharStreams.fromString(sql);
		SqlBaseLexer lexer1 = new SqlBaseLexer(stream);
		CommonTokenStream tokens1 = new CommonTokenStream(lexer1);
		SqlBaseParser parser1 = new SqlBaseParser(tokens1);
		parser1.getInterpreter().setPredictionMode(PredictionMode.SLL);
		parser1.removeErrorListeners(); // 移除所有错误的监听
		// parser1.addErrorListener();

		ParseTree tree = null;
		try 
			tree = parser1.singleStatement(); // STAGE 1
		 catch (Exception ex) 
			ex.printStackTrace();
		

		SimpleSqlTreeVisitor simpleSqlTreeVisitor = new SimpleSqlTreeVisitor();
		if (tree != null) 
			SelectSql selectSql = simpleSqlTreeVisitor.visit(tree);
			System.out.println(selectSql);
			if (!selectSql.isQuery()) 
				throw new RuntimeException("当前执行的sql不是查询!");
			
			System.out.print(selectSql.startToken);
			System.out.print(" " + selectSql.fields.stream().collect(Collectors.joining(",")));

			if (selectSql.from) 
				System.out.print(" FROM");
				System.out.print(" " + selectSql.tableName);
			
			System.out.println();
		
	

	private static class SelectSql 
		private String startToken;
		private boolean from;
		private List<String> fields;
		private String tableName;

		public boolean isQuery() 
			return "SELECT".equalsIgnoreCase(startToken);
		
	

	private static class SimpleSqlTreeVisitor implements ParseTreeVisitor<SelectSql> 
		SelectSql selectSql;

		SimpleSqlTreeVisitor() 
			selectSql = new SelectSql();
			selectSql.fields = new ArrayList<>();
		

		@Override
		public SelectSql visit(ParseTree tree) 
			int childCount = tree.getChildCount();
			for (int i = 0; i < childCount; i++) 
				ParseTree child = tree.getChild(i);
				if (child instanceof StatementContext) 
					StatementContext statementContext = (StatementContext) child;
					System.out.println("statementContext");
					handlerWithStatement(statementContext);
				
			
			return selectSql;
		

		private void handlerWithStatement(StatementContext statementContext) 
			int childCount = statementContext.getChildCount(); // 必定以select开头
			int i = 0;
			for (; i < childCount; i++) 
				ParseTree child = statementContext.getChild(i);
				if (child instanceof SelectElementsContext) 
					System.out.println("SelectElementsContext");
					selectSql.fields.add(child.getText());// 这里应该是有多个
				 else if (child instanceof SqlBaseParser.FromClauseContext) 
					System.out.println("FromClauseContext");
					selectSql.tableName = child.getChild(1).getText();
					selectSql.from = true;
				 else if (child instanceof TerminalNode) 
					selectSql.startToken = child.getText(); // 这个就是SELECT
					// System.out.println("TerminalNode==>" + terminalNode);
				

			
		

		@Override
		public SelectSql visitChildren(RuleNode node) 
			// TODO Auto-generated method stub
			return null;
		

		@Override
		public SelectSql visitTerminal(TerminalNode node) 
			// TODO Auto-generated method stub
			return null;
		

		@Override
		public SelectSql visitErrorNode(ErrorNode node) 
			// TODO Auto-generated method stub
			return null;
		

	


注意这里的解析方式实际上就是和自己定义的g4文件有关系,是按照树的样式来解的

得到的结果:

发现手动解析语法还是要手动判断的

5. 总结

  1. 对于antlr4的使用是需要对g4的文件格式必须了解,要会编写对应的文件
  2. antlr4对于解析语法还是比较简单的
  3. 对于将解析的语法转换为实体类还是比较麻烦的(还是要根据对应的g4格式来解析)

以上是关于Antlr4之简单的sql查询解析demo的主要内容,如果未能解决你的问题,请参考以下文章

Antlr4之简单的sql查询解析demo

Spark SQL源码解析Antlr4解析Sql并生成树

使用Antlr4和neo4j解析sql生成数据地图

ANTLR4 如何编写语法文件之语法解析器规则

ANTLR4 如何编写语法文件之语法解析器规则

ANTLR4入门:使用mave ANTLR4插件(antlr4-maven-plugin)执行语法解析生成器