java - 如何使用 ANTRL4 解析 Clickhouse-SQL 语句?
问题描述
目标:向任何给定的 Clickhouse 语句添加附加WHERE
子句。
我正在使用以下 Antlr 语法为词法分析器和解析器生成 Java 类。
词法分析器语法
https://github.com/ClickHouse/ClickHouse/blob/master/utils/antlr/ClickHouseLexer.g4
解析器语法
https://github.com/ClickHouse/ClickHouse/blob/master/utils/antlr/ClickHouseParser.g4
问题:我无法弄清楚/理解如何与 Antlr 生成的生成类交互或创建适当的 POJO。
语句示例
String query = "INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def')"
SQL 的目标(扩充代码)
String enrichedQuery = SqlParser.enrich(query);
System.out.println(enrichedQuery);
//Output
>> INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def') (WHERE X IN USERS)
我有以下 Java 主要内容
public class Hello {
public static void main( String[] args) throws Exception{
String query = "INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def')"
ClickhouseLexer = new ClickhouseLexer(new ANTLRInputStream(query));
CommonTokenStream tokens = new CommonTokenStream(lexer);
ClickHouseParser = new ClickHouseParser (tokens);
ParseTreeWalker walker = new ParseTreeWalker();
}
}
解决方案
我建议看看TokenStreamRewriter。
首先,让我们准备好语法。
1 -TokenStreamRewriter
我们想要保留空格,所以让我们将-> skip
指令更改为->channel(HIDDEN)
在 Lexer 语法的末尾:
// Comments and whitespace
MULTI_LINE_COMMENT: '/*' .*? '*/' -> channel(HIDDEN);
SINGLE_LINE_COMMENT: '--' ~('\n'|'\r')* ('\n' | '\r' | EOF) -> channel(HIDDEN);
WHITESPACE: [ \u000B\u000C\t\r\n] -> channel(HIDDEN); // '\n' can be part of multiline single query
2 - C++ 特定的东西只是防止多次使用关键字。您实际上并不需要出于您的目的进行该检查(如果您确实需要,可以在解析后的侦听器中完成)。所以让我们丢掉语言特定的东西:
engineClause: engineExpr (
orderByClause
| partitionByClause
| primaryKeyClause
| sampleByClause
| ttlClause
| settingsClause
)*
;
和
dictionaryAttrDfnt
: identifier columnTypeExpr (
DEFAULT literal
| EXPRESSION columnExpr
| HIERARCHICAL
| INJECTIVE
| IS_OBJECT_ID
)*
;
dictionaryEngineClause
: dictionaryPrimaryKeyClause? (
sourceClause
| lifetimeClause
| layoutClause
| rangeClause
| dictionarySettingsClause
)*
;
注意:似乎存在语法不接受插入语句的实际值的问题:
insertStmt
: INSERT INTO TABLE? (
tableIdentifier
| FUNCTION tableFunctionExpr
) columnsClause? dataClause
;
columnsClause
: LPAREN nestedIdentifier (COMMA nestedIdentifier)* RPAREN
;
dataClause
: FORMAT identifier # DataClauseFormat
| VALUES # DataClauseValues // <- problem on this line
| selectUnionStmt SEMICOLON? EOF # DataClauseSelect
;
(我不会尝试修复那部分,所以我评论了你的意见以适应)
(如果顶层规则需要一个EOF
令牌,这也会有所帮助;没有那个 ANTLR 只是在 . 之后停止解析VALUE
。正是由于这个原因,根规则末尾的 EOF 被认为是最佳实践。)
主程序:
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStreamRewriter;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
public class TSWDemo {
public static void main(String... args) {
new TSWDemo().run(CharStreams.fromString("INSERT INTO t VALUES /* (1, 'Hello, world'), (2, 'abc'), (3, 'def') */"));
}
public void run(CharStream charStream) {
var lexer = new ClickHouseLexer(charStream);
var tokenStream = new CommonTokenStream(lexer);
var parser = new ClickHouseParser(tokenStream);
var tsw = new TokenStreamRewriter(tokenStream);
var listener = new TSWDemoListener(tsw);
var queryStmt = parser.queryStmt();
ParseTreeWalker.DEFAULT.walk(listener, queryStmt);
System.out.println(tsw.getText());
}
}
听者:
import org.antlr.v4.runtime.TokenStreamRewriter;
public class TSWDemoListener extends ClickHouseParserBaseListener {
private TokenStreamRewriter tsw;
public TSWDemoListener(TokenStreamRewriter tsw) {
this.tsw = tsw;
}
@Override
public void exitInsertStmt(ClickHouseParser.InsertStmtContext ctx) {
tsw.insertAfter(ctx.getStop(), " (WHERE X IN USERS)");
}
}
输出:
INSERT INTO t VALUES (WHERE X IN USERS) /* (1, 'Hello, world'), (2, 'abc'), (3, 'def') */
推荐阅读
- opencv - vcap 无法打开 [udp @ 0x56378e8a76a0] 绑定失败:权限被拒绝
- rust - 如何将 main 中初始化的变量传递给 Rocket 路由处理程序?
- c# - 如何仅通过更改 1 或 2 行代码来修复以下 C# 代码中的错误?
- go - 如何自定义 go-present 模板?
- r - R 中的 Lattice 程序可能会导致加载其他包和库时出错
- latex - 如何使用 maketitle 使标题看起来像这样
- django - 如何在 Django 中为页面添加书签?
- javascript - 在 JavaScript 中对字符串进行编码和解码
- php - limit_text 只输出纯文本,不显示粗体
- android - 由于一些开销,将 OpenCL 与 Android JNI 一起使用会产生缓慢的代码