java:antlr4mysql获取单个语句

n9vozmp4  于 2021-06-21  发布在  Mysql
关注(0)|答案(2)|浏览(276)

我使用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和手动解析代码是新手。我在这里找不到任何关于我需要做些什么才能使单个sql语句脱离数据库的参考资料 MySqlParser . 下一步我需要做什么?

cyej8jka

cyej8jka1#

解析器不是解决这类问题的合适工具。语句拆分器很容易手动编写,如果您自己编写的话,速度会更快。我在mysql工作台上用c++实现了这样一个拆分器。把它移植到java应该不难。代码非常快(在一台普通机器上,不到1秒就有1百万个loc sql代码)。解析器需要更长的时间。

l7wslrjt

l7wslrjt2#

但是,我确信这是可以改进的,因为创建它的最简单的方法是创建一个侦听器并为构造函数提供一个 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);
        }
    }
}

接下来需要遍历语句,前面的字符串使用者允许您在需要的地方延迟地重定向输出。这可以像打印到stdout一样简单,但是也可以很容易地用于附加到列表。

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());
}

相关问题