如何使用 ANTRL4 解析 Clickhouse-SQL 语句?
How to parse a Clickhouse-SQL statement using ANTRL4?
Objective : 向任何给定的 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
问题:我不知道out/understand如何交互或创建适当的 POJO 以与 Antlr 生成的生成的 类 一起使用。
语句示例
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++ 特定的东西只是防止多次使用关键字。你真的不需要为了你的目的而进行检查(如果你确实需要它,它可以在 post-parse Listener 中完成)。所以让我们丢掉特定于语言的东西:
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') */
Objective : 向任何给定的 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
问题:我不知道out/understand如何交互或创建适当的 POJO 以与 Antlr 生成的生成的 类 一起使用。
语句示例
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++ 特定的东西只是防止多次使用关键字。你真的不需要为了你的目的而进行检查(如果你确实需要它,它可以在 post-parse Listener 中完成)。所以让我们丢掉特定于语言的东西:
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') */