Я использую 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
. Что мне делать дальше?
Парсер не подходит для решения такого рода проблем. Разделитель операторов довольно легко написать вручную и намного быстрее, если вы сделаете это самостоятельно. Я реализовал такой сплиттер на C++ в MySQL Workbench. Перенести это на Java не составит труда. Код очень быстрый (1 миллион 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());
}
Вы профилировали свое решение? Сколько времени требуется, например, для создания файла в 1 миллион строк?
У вас есть файл размером 1 миллион, который я могу использовать? Я сделаю это, если у вас есть такая возможность, но, как я уже сказал, я хочу стать лучше в Antlr. Как только я пойму, как правильно использовать Antlr, я смогу оптимизировать. В противном случае такая оптимизация преждевременна. Хотя я могу оценить ваше решение, которое очень быстро разбивает сценарии на отдельные операторы, меня больше беспокоит общее решение, которое я могу расширить (например, несколько диалектов SQL или даже что-то, не связанное с SQL), а не одноразовое решение. это применимо только к этому индивидуальному случаю и не учит меня больше о языковой обработке / синтаксическом анализе.
Я ценю ваш ответ, но на самом деле он мне не помогает. Я использовал подобные простые решения и раньше, однако я хочу лучше использовать Antlr, поэтому я старался изо всех сил его использовать. К большому сожалению, вероятно, есть люди, которым приходится использовать Antlr для своих проектов, и они могут наткнуться на этот ответ при поиске ресурсов, которые им тоже не помогут.