我正在使用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);
}
}
}
接下来你需要遍历语句,前面的字符串消费者允许你懒惰地将输出重定向到你需要的地方。这可以像打印到 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());
}