Java: Antlr4 MySql 获取个别语句

Java: Antlr4 MySql get individual statements

我正在使用 Java 和 JDBC 到 运行 MySql 代码。我想执行一个DDL脚本,但是JDBC一次只能执行一条语句,这使得它不适合开箱即用地执行整个.sql文件。

我想做的是使用 Antlr4 来解析 .sql 文件,这样我就可以分解每个单独的语句,然后用 JDBC.

迭代地执行它们

我已经走到这一步了:

InputStream resourceAsStream = Main.class.getClassLoader()
            .getResourceAsStream("an-arbitrary-ddl.sql");
CharStream codePointCharStream = CharStreams.fromStream(resourceAsStream);
MySqlLexer tokenSource = new MySqlLexer(new CaseChangingCharStream(codePointCharStream, true));
TokenStream tokenStream = new CommonTokenStream(tokenSource);
MySqlParser mySqlParser = new MySqlParser(tokenStream);
// Where do I go from here?

我确定我没有在搜索正确的术语,因为我是 Antlr 的新手并且手动解析代码。我无法从这里找到任何关于我需要做什么才能从 MySqlParser 中获取单个 sql 语句的参考。接下来我需要做什么?

解析器不是解决此类问题的正确工具。语句拆分器很容易手动编写,如果您自己编写,速度会快得多。我在 MySQL Workbench 中用 C++ 实现了这样一个拆分器。将其移植到 Java 应该不难。代码非常快(1 Mio LOC SQL 代码在普通机器上不到 1 秒)。解析器需要 的时间。

我确信这可以改进,但是,因为我可以创建它的最简单方法是创建一个侦听器并为构造函数提供一个 Consumer<String> 对象。侦听器查看各个语句并递归地构造它们。可能有更优化的解决方案,但是,如果有的话,我不再有时间尝试优化它。

/**
 * @author Paul Nelson Baker
 * @see <a href="https://github.com/paul-nelson-baker/">GitHub</a>
 * @see <a href="https://www.linkedin.com/in/paul-n-baker/">LinkedIn</a>
 * @since 2018-09
 */
public class SqlStatementListener extends MySqlParserBaseListener {

    private final Consumer<String> sqlStatementConsumer;

    public SqlStatementListener(Consumer<String> sqlStatementConsumer) {
        this.sqlStatementConsumer = sqlStatementConsumer;
    }

    @Override
    public void enterSqlStatement(MySqlParser.SqlStatementContext ctx) {
        if (ctx.getChildCount() > 0) {
            StringBuilder stringBuilder = new StringBuilder();
            recreateStatementString(ctx.getChild(0), stringBuilder);
            stringBuilder.setCharAt(stringBuilder.length() - 1, ';');
            String recreatedSqlStatement = stringBuilder.toString();
            sqlStatementConsumer.accept(recreatedSqlStatement);
        }
        super.enterSqlStatement(ctx);
    }

    private void recreateStatementString(ParseTree currentNode, StringBuilder stringBuilder) {
        if (currentNode instanceof TerminalNode) {
            stringBuilder.append(currentNode.getText());
            stringBuilder.append(' ');
        }
        for (int i = 0; i < currentNode.getChildCount(); i++) {
            recreateStatementString(currentNode.getChild(i), stringBuilder);
        }
    }
}

接下来你需要遍历语句,前面的字符串消费者允许你懒惰地将输出重定向到你需要的地方。这可以像打印到标准输出一样简单,但是,它可以很容易地用于附加到列表。

public List<String> mySqlStatementsFrom(String sourceCode) {
    List<String> statements = new ArrayList<>();
    mySqlStatementsToConsumer(sourceCode, statements::add);
    return statements;
}

public void mySqlStatementsToConsumer(String sourceCode, Consumer<String> mySqlStatementConsumer) {
    CharStream codePointCharStream = CharStreams.fromString(sourceCode);
    MySqlLexer tokenSource = new MySqlLexer(new CaseChangingCharStream(codePointCharStream, true));
    TokenStream tokenStream = new CommonTokenStream(tokenSource);
    MySqlParser mySqlParser = new MySqlParser(tokenStream);

    SqlStatementListener statementListener = new SqlStatementListener(mySqlStatementConsumer);
    ParseTreeWalker.DEFAULT.walk(statementListener, mySqlParser.sqlStatements());
}