我正在使用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秒内1 Mio LOC SQL代码)。解析器需要更长的时间。
我确信这可以改进,但是,我可以创建一个最简单的方法是创建一个监听器并为构造函数提供一个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());
}