【问题标题】:How to parse a Clickhouse-SQL statement using ANTRL4?如何使用 ANTRL4 解析 Clickhouse-SQL 语句?
【发布时间】:2021-10-11 16:07:19
【问题描述】:

目标:在任何给定的 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();
    }
}

【问题讨论】:

  • 首先,编写一个只解析输入字符串的驱动程序。将上述 main() 中的“HelloParser”替换为“ClickHouseParser”,将“HelloLexer”替换为“ClickHouseLexer”。测试一下,然后你就可以为你的目标修改解析器树了。
  • @kaby76 感谢您的留言。请参阅修改后的代码。我错误地用手卡住了我想要达到的目标的快速概要。此后已在您发送消息之前进行了更新
  • 试试var str = CharStreams.fromString(input); var lexer = new ClickHouseLexer(str); var tokens = new CommonTokenStream(lexer); var parser = new ClickHouseParser(tokens); var tree = parser.queryStmt();。但是解析器语法针对的是 C,而不是 Java。因此,您必须更改解析器语法:在顶部的options {...} 之后添加@header { import java.util.Set; }std::set<std::string> attrs改为Set<String> attrsattrs.count(改为attrs.contains(attrs.insert( 更改为 attrs.add(clauses 类似。
  • 有几种方法可以实现 where 子句,但一个简单的解决方案是只编写一个树遍历器(可能您可以使用生成的 Antlr 语法侦听器)在每个叶子处输出原始代码,但是当您到达出现where 的特定节点时,输出该代码。
  • 由于子查询,不清楚您要添加哪个where 子句。仅用于顶级选择吗?这是否仅适用于 select 语句或其他可以有 where 子句的语句?

标签: java sql antlr4 clickhouse


【解决方案1】:

我建议看看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') */

【讨论】:

  • 这是最有帮助的,迈克。感谢您的详尽解释
猜你喜欢
  • 2021-06-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 2023-03-04
相关资源
最近更新 更多