From c3b65ac2c3f04cd6cc98841d8abc49bd0920ba55 Mon Sep 17 00:00:00 2001 From: rkuchar Date: Wed, 2 May 2018 13:08:08 +0200 Subject: [PATCH] DBZ-252 add parser listeners for alter view and drop view statements + test for it --- .../io/debezium/antlr/AntlrDdlParser.java | 10 +- .../antlr/mysql/MySqlAntlrDdlParser.java | 6 +- .../listener/AlterViewParserListener.java | 77 +++++++++++++ .../listener/CreateViewParserListener.java | 6 +- .../listener/DropViewParserListener.java | 32 ++++++ .../listener/MySqlAntlrDdlParserListener.java | 2 + .../antlr/mysql/MySqlAntlrDdlParserTest.java | 106 +++++++++++++++++- 7 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/AlterViewParserListener.java create mode 100644 debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/DropViewParserListener.java diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/AntlrDdlParser.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/AntlrDdlParser.java index 8accd9d37..f070feec7 100644 --- a/debezium-ddl-parser/src/main/java/io/debezium/antlr/AntlrDdlParser.java +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/AntlrDdlParser.java @@ -37,7 +37,11 @@ public abstract class AntlrDdlParser extends protected DataTypeResolver dataTypeResolver = new DataTypeResolver(); public AntlrDdlParser(boolean throwErrorsFromTreeWalk) { - super(";"); + this(throwErrorsFromTreeWalk, false); + } + + public AntlrDdlParser(boolean throwErrorsFromTreeWalk, boolean includeViews) { + super(";", includeViews); this.throwErrorsFromTreeWalk = throwErrorsFromTreeWalk; } @@ -196,8 +200,8 @@ public void signalAlterTable(TableId id, TableId previousId, ParserRuleContext c } @Override - public void signalDropTable(TableId id, String ctx) { - super.signalDropTable(id, ctx); + public void signalDropTable(TableId id, String statement) { + super.signalDropTable(id, statement); } /** diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/MySqlAntlrDdlParser.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/MySqlAntlrDdlParser.java index 209390ab7..c215077a5 100644 --- a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/MySqlAntlrDdlParser.java +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/MySqlAntlrDdlParser.java @@ -43,7 +43,11 @@ public MySqlAntlrDdlParser() { } public MySqlAntlrDdlParser(boolean throwErrorsFromTreeWalk) { - super(throwErrorsFromTreeWalk); + this(throwErrorsFromTreeWalk, false); + } + + public MySqlAntlrDdlParser(boolean throwErrorsFromTreeWalk, boolean includeViews) { + super(throwErrorsFromTreeWalk, includeViews); systemVariables = new MySqlSystemVariables(); } diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/AlterViewParserListener.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/AlterViewParserListener.java new file mode 100644 index 000000000..096512571 --- /dev/null +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/AlterViewParserListener.java @@ -0,0 +1,77 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package io.debezium.antlr.mysql.listener; + +import io.debezium.antlr.AntlrDdlParser; +import io.debezium.antlr.mysql.MySqlAntlrDdlParser; +import io.debezium.ddl.parser.mysql.generated.MySqlParser; +import io.debezium.ddl.parser.mysql.generated.MySqlParserBaseListener; +import io.debezium.relational.Column; +import io.debezium.relational.TableEditor; +import io.debezium.relational.TableId; +import io.debezium.text.ParsingException; +import org.antlr.v4.runtime.tree.ParseTreeListener; + +import java.util.List; + +/** + * @author Roman Kuchár . + */ +public class AlterViewParserListener extends MySqlParserBaseListener { + + private final MySqlAntlrDdlParser parserCtx; + private final List listeners; + + private TableEditor tableEditor; + private ViewSelectedColumnsParserListener selectColumnsListener; + + + public AlterViewParserListener(MySqlAntlrDdlParser parserCtx, List listeners) { + this.parserCtx = parserCtx; + this.listeners = listeners; + } + + @Override + public void enterAlterView(MySqlParser.AlterViewContext ctx) { + if (!parserCtx.skipViews()) { + TableId tableId = parserCtx.parseQualifiedTableId(ctx.fullId()); + + tableEditor = parserCtx.databaseTables().editTable(tableId); + if (tableEditor == null) { + throw new ParsingException(null, "Trying to alter view " + parserCtx.getFullTableName(tableId) + + ", which does not exists. Query:" + AntlrDdlParser.getText(ctx)); + } + // alter view will override existing columns for a new one + tableEditor.columnNames().forEach(tableEditor::removeColumn); + // create new columns just with specified name for now + if (ctx.uidList() != null) { + ctx.uidList().uid().stream().map(parserCtx::parseName).forEach(columnName -> { + tableEditor.addColumn(Column.editor().name(columnName).create()); + }); + } + selectColumnsListener = new ViewSelectedColumnsParserListener(tableEditor, parserCtx); + listeners.add(selectColumnsListener); + } + super.enterAlterView(ctx); + } + + @Override + public void exitAlterView(MySqlParser.AlterViewContext ctx) { + parserCtx.runIfNotNull(() -> { + tableEditor.addColumns(selectColumnsListener.getSelectedColumns()); + // Make sure that the table's character set has been set ... + if (!tableEditor.hasDefaultCharsetName()) { + tableEditor.setDefaultCharsetName(parserCtx.currentDatabaseCharset()); + } + parserCtx.databaseTables().overwriteTable(tableEditor.create()); + listeners.remove(selectColumnsListener); + }, tableEditor); + // signal view even if it was skipped + parserCtx.signalAlterView(parserCtx.parseQualifiedTableId(ctx.fullId()), null, ctx); + super.exitAlterView(ctx); + } +} diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/CreateViewParserListener.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/CreateViewParserListener.java index c6dff4fc6..63f479dc3 100644 --- a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/CreateViewParserListener.java +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/CreateViewParserListener.java @@ -37,8 +37,8 @@ public void enterCreateView(MySqlParser.CreateViewContext ctx) { tableEditor = parserCtx.databaseTables().editOrCreateTable(parserCtx.parseQualifiedTableId(ctx.fullId())); // create new columns just with specified name for now if (ctx.uidList() != null) { - ctx.uidList().uid().forEach(uidContext -> { - tableEditor.addColumn(Column.editor().name(parserCtx.parseName(uidContext)).create()); + ctx.uidList().uid().stream().map(parserCtx::parseName).forEach(columnName -> { + tableEditor.addColumn(Column.editor().name(columnName).create()); }); } selectColumnsListener = new ViewSelectedColumnsParserListener(tableEditor, parserCtx); @@ -58,7 +58,7 @@ public void exitCreateView(MySqlParser.CreateViewContext ctx) { parserCtx.databaseTables().overwriteTable(tableEditor.create()); listeners.remove(selectColumnsListener); }, tableEditor); - // TODO rkuchar move into lambda function + // signal view even if it was skipped parserCtx.signalCreateView(parserCtx.parseQualifiedTableId(ctx.fullId()), ctx); super.exitCreateView(ctx); } diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/DropViewParserListener.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/DropViewParserListener.java new file mode 100644 index 000000000..fc7cd6786 --- /dev/null +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/DropViewParserListener.java @@ -0,0 +1,32 @@ +/* + * Copyright Debezium Authors. + * + * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ + +package io.debezium.antlr.mysql.listener; + +import io.debezium.antlr.mysql.MySqlAntlrDdlParser; +import io.debezium.ddl.parser.mysql.generated.MySqlParser; +import io.debezium.ddl.parser.mysql.generated.MySqlParserBaseListener; + +/** + * @author Roman Kuchár . + */ +public class DropViewParserListener extends MySqlParserBaseListener { + + private final MySqlAntlrDdlParser parserCtx; + + public DropViewParserListener(MySqlAntlrDdlParser parserCtx) { + this.parserCtx = parserCtx; + } + + @Override + public void enterDropView(MySqlParser.DropViewContext ctx) { + ctx.fullId().stream().map(parserCtx::parseQualifiedTableId).forEach(tableId -> { + parserCtx.databaseTables().removeTable(tableId); + parserCtx.signalDropView(tableId, ctx); + }); + super.enterDropView(ctx); + } +} diff --git a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/MySqlAntlrDdlParserListener.java b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/MySqlAntlrDdlParserListener.java index 4874f8716..866bbf262 100644 --- a/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/MySqlAntlrDdlParserListener.java +++ b/debezium-ddl-parser/src/main/java/io/debezium/antlr/mysql/listener/MySqlAntlrDdlParserListener.java @@ -49,6 +49,8 @@ public MySqlAntlrDdlParserListener(MySqlAntlrDdlParser parserCtx) { listeners.add(new RenameTableParserListener(parserCtx)); listeners.add(new TruncateTableParserListener(parserCtx)); listeners.add(new CreateViewParserListener(parserCtx, listeners)); + listeners.add(new AlterViewParserListener(parserCtx, listeners)); + listeners.add(new DropViewParserListener(parserCtx)); listeners.add(new CreateUniqueIndexParserListener(parserCtx)); listeners.add(new SetStatementParserListener(parserCtx)); listeners.add(new UseStatementParserListener(parserCtx)); diff --git a/debezium-ddl-parser/src/test/java/io/debezium/antlr/mysql/MySqlAntlrDdlParserTest.java b/debezium-ddl-parser/src/test/java/io/debezium/antlr/mysql/MySqlAntlrDdlParserTest.java index 3e68957d9..c6d3ee638 100644 --- a/debezium-ddl-parser/src/test/java/io/debezium/antlr/mysql/MySqlAntlrDdlParserTest.java +++ b/debezium-ddl-parser/src/test/java/io/debezium/antlr/mysql/MySqlAntlrDdlParserTest.java @@ -107,6 +107,102 @@ public void shouldParseCreateTableStatementWithSingleGeneratedAndPrimaryKeyColum assertColumn(foo, "c2", "VARCHAR", Types.VARCHAR, 22, -1, true, false, false); } + @Test + public void shouldParseCreateViewStatementStartSelect() { + String ddl = "CREATE TABLE foo ( " + System.lineSeparator() + + " c1 INTEGER NOT NULL AUTO_INCREMENT, " + System.lineSeparator() + + " c2 VARCHAR(22) " + System.lineSeparator() + + "); " + System.lineSeparator(); + String ddl2 = "CREATE VIEW fooView AS (SELECT * FROM foo)" + System.lineSeparator(); + + parser = new MysqlDdlParserWithSimpleTestListener(listener, true); + parser.parse(ddl, tables); + parser.parse(ddl2, tables); + assertThat(tables.size()).isEqualTo(2); + Table foo = tables.forTable(new TableId(null, null, "fooView")); + assertThat(foo).isNotNull(); + assertThat(foo.columnNames()).containsExactly("c1", "c2"); + assertThat(foo.primaryKeyColumnNames()).isEmpty(); + assertColumn(foo, "c1", "INTEGER", Types.INTEGER, -1, -1, false, true, true); + assertColumn(foo, "c2", "VARCHAR", Types.VARCHAR, 22, -1, true, false, false); + } + + @Test + public void shouldParseDropView() { + String ddl = "CREATE TABLE foo ( " + System.lineSeparator() + + " c1 INTEGER NOT NULL AUTO_INCREMENT, " + System.lineSeparator() + + " c2 VARCHAR(22) " + System.lineSeparator() + + "); " + System.lineSeparator(); + String ddl2 = "CREATE VIEW fooView AS (SELECT * FROM foo)" + System.lineSeparator(); + String ddl3 = "DROP VIEW fooView"; + parser = new MysqlDdlParserWithSimpleTestListener(listener, true); + parser.parse(ddl, tables); + parser.parse(ddl2, tables); + parser.parse(ddl3, tables); + assertThat(tables.size()).isEqualTo(1); + Table foo = tables.forTable(new TableId(null, null, "fooView")); + assertThat(foo).isNull(); + } + + @Test + public void shouldParseCreateViewStatementColumnAlias() { + String ddl = "CREATE TABLE foo ( " + System.lineSeparator() + + " c1 INTEGER NOT NULL AUTO_INCREMENT, " + System.lineSeparator() + + " c2 VARCHAR(22) " + System.lineSeparator() + + "); " + System.lineSeparator(); + String ddl2 = "CREATE VIEW fooView(w1) AS (SELECT c2 as w1 FROM foo)" + System.lineSeparator(); + + parser = new MysqlDdlParserWithSimpleTestListener(listener, true); + parser.parse(ddl, tables); + parser.parse(ddl2, tables); + assertThat(tables.size()).isEqualTo(2); + Table foo = tables.forTable(new TableId(null, null, "fooView")); + assertThat(foo).isNotNull(); + assertThat(foo.columnNames()).containsExactly("w1"); + assertThat(foo.primaryKeyColumnNames()).isEmpty(); + assertColumn(foo, "w1", "VARCHAR", Types.VARCHAR, 22, -1, true, false, false); + } + + @Test + public void shouldParseCreateViewStatementColumnAliasInnerSelect() { + String ddl = "CREATE TABLE foo ( " + System.lineSeparator() + + " c1 INTEGER NOT NULL AUTO_INCREMENT, " + System.lineSeparator() + + " c2 VARCHAR(22) " + System.lineSeparator() + + "); " + System.lineSeparator(); + String ddl2 = "CREATE VIEW fooView(w1) AS (SELECT foo2.c2 as w1 FROM (SELECT c1 as c2 FROM foo) AS foo2)" + System.lineSeparator(); + + parser = new MysqlDdlParserWithSimpleTestListener(listener, true); + parser.parse(ddl, tables); + parser.parse(ddl2, tables); + assertThat(tables.size()).isEqualTo(2); + Table foo = tables.forTable(new TableId(null, null, "fooView")); + assertThat(foo).isNotNull(); + assertThat(foo.columnNames()).containsExactly("w1"); + assertThat(foo.primaryKeyColumnNames()).isEmpty(); + assertColumn(foo, "w1", "INTEGER", Types.INTEGER, -1, -1, false, true, true); + } + + @Test + public void shouldParseAlterViewStatementColumnAliasInnerSelect() { + String ddl = "CREATE TABLE foo ( " + System.lineSeparator() + + " c1 INTEGER NOT NULL AUTO_INCREMENT, " + System.lineSeparator() + + " c2 VARCHAR(22) " + System.lineSeparator() + + "); " + System.lineSeparator(); + String ddl2 = "CREATE VIEW fooView(w1) AS (SELECT foo2.c2 as w1 FROM (SELECT c1 as c2 FROM foo) AS foo2)" + System.lineSeparator(); + String ddl3 = "ALTER VIEW fooView AS (SELECT c2 FROM foo)"; + parser = new MysqlDdlParserWithSimpleTestListener(listener, true); + parser.parse(ddl, tables); + parser.parse(ddl2, tables); + parser.parse(ddl3, tables); + assertThat(tables.size()).isEqualTo(2); + assertThat(listener.total()).isEqualTo(3); + Table foo = tables.forTable(new TableId(null, null, "fooView")); + assertThat(foo).isNotNull(); + assertThat(foo.columnNames()).containsExactly("c2"); + assertThat(foo.primaryKeyColumnNames()).isEmpty(); + assertColumn(foo, "c2", "VARCHAR", Types.VARCHAR, 22, -1, true, false, false); + } + @Test public void shouldParseCreateTableStatementWithSingleGeneratedColumnAsPrimaryKey() { String ddl = "CREATE TABLE my.foo ( " + System.lineSeparator() @@ -755,7 +851,9 @@ public void shouldParseTestStatements() { // legacy parser was signaling all created index // antlr is parsing only those, which will make any model changes int numberOfNonUniqueIndexesCreated = 2; - assertThat(listener.total()).isEqualTo(58 - numberOfAlteredTablesWhichDoesNotExists - numberOfNonUniqueIndexesCreated); + int numberOfAlterViewStatements = 6; + assertThat(listener.total()).isEqualTo(58 - numberOfAlteredTablesWhichDoesNotExists + - numberOfNonUniqueIndexesCreated + numberOfAlterViewStatements); listener.forEach(this::printEvent); } @@ -1563,7 +1661,11 @@ protected void assertColumn(Table table, String name, String typeName, int jdbcT class MysqlDdlParserWithSimpleTestListener extends MySqlAntlrDdlParser { public MysqlDdlParserWithSimpleTestListener(DdlChanges changesListener) { - super(false); + this(changesListener, false); + } + + public MysqlDdlParserWithSimpleTestListener(DdlChanges changesListener, boolean includeViews) { + super(false, includeViews); this.ddlChanges = changesListener; } }