DBZ-252 introduce proxy parse tree listener

This commit is contained in:
rkuchar 2018-03-14 20:52:55 +01:00 committed by Gunnar Morling
parent b92e8ef760
commit 01297aebc9
2 changed files with 260 additions and 111 deletions

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
*
* Licensed under the BSD 3-clause license, available at https://raw.githubusercontent.com/antlr/antlr4/master/LICENSE.txt
*/
package io.debezium.antlr;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTreeListener;
import org.antlr.v4.runtime.tree.TerminalNode;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Instances of this class allows multiple listeners to receive events
* while walking the parse tree. For example:
*
* <pre>
* ProxyParseTreeListener proxy = new ProxyParseTreeListener();
* ParseTreeListener listener1 = ... ;
* ParseTreeListener listener2 = ... ;
* proxy.add( listener1 );
* proxy.add( listener2 );
* ParseTreeWalker.DEFAULT.walk( proxy, ctx );
* </pre>
*/
public class ProxyParseTreeListener implements ParseTreeListener {
private List<ParseTreeListener> listeners;
/**
* Creates a new proxy without an empty list of listeners. Add
* listeners before walking the tree.
*/
public ProxyParseTreeListener() {
// Setting the listener to null automatically instantiates a new list.
this( null );
}
/**
* Creates a new proxy with the given list of listeners.
*
* @param listeners A list of listerners to receive events.
*/
public ProxyParseTreeListener( List<ParseTreeListener> listeners ) {
setListeners( listeners );
}
@Override
public void enterEveryRule( ParserRuleContext ctx ) {
for( ParseTreeListener listener : getListeners() ) {
listener.enterEveryRule( ctx );
ctx.enterRule( listener );
}
}
@Override
public void exitEveryRule( ParserRuleContext ctx ) {
for( ParseTreeListener listener : getListeners() ) {
ctx.exitRule( listener );
listener.exitEveryRule( ctx );
}
}
@Override
public void visitErrorNode( ErrorNode node ) {
for( ParseTreeListener listener : getListeners() ) {
listener.visitErrorNode( node );
}
}
@Override
public void visitTerminal( TerminalNode node ) {
for( ParseTreeListener listener : getListeners() ) {
listener.visitTerminal( node );
}
}
/**
* Adds the given listener to the list of event notification recipients.
*
* @param listener A listener to begin receiving events.
*/
public void add( ParseTreeListener listener ) {
getListeners().add( listener );
}
/**
* Removes the given listener to the list of event notification recipients.
*
* @param listener A listener to stop receiving events.
* @return false The listener was not registered to receive events.
*/
public boolean remove( ParseTreeListener listener ) {
return getListeners().remove( listener );
}
/**
* Returns the list of listeners.
*
* @return The list of listeners to receive tree walking events.
*/
private List<ParseTreeListener> getListeners() {
return this.listeners;
}
/**
* Changes the list of listeners to receive events. If the given list of
* listeners is null, an empty list will be created.
*
* @param listeners A list of listeners to receive tree walking
* events.
*/
public void setListeners( List<ParseTreeListener> listeners ) {
if( listeners == null ) {
listeners = createParseTreeListenerList();
}
this.listeners = listeners;
}
/**
* Creates a CopyOnWriteArrayList to permit concurrent mutative
* operations.
*
* @return A thread-safe, mutable list of event listeners.
*/
protected List<ParseTreeListener> createParseTreeListenerList() {
return new CopyOnWriteArrayList<ParseTreeListener>();
}
}

View File

@ -7,6 +7,7 @@
package io.debezium.antlr.mysql;
import io.debezium.antlr.AntlrDdlParser;
import io.debezium.antlr.ProxyParseTreeListener;
import io.debezium.ddl.parser.mysql.generated.MySqlLexer;
import io.debezium.ddl.parser.mysql.generated.MySqlParser;
import io.debezium.ddl.parser.mysql.generated.MySqlParserBaseListener;
@ -33,12 +34,22 @@ public class MySqlAntlrDdlParser extends AntlrDdlParser<MySqlLexer, MySqlParser>
private Tables databaseTables;
private TableEditor tableEditor;
private ColumnEditor columnEditor;
@Override
protected void parse(MySqlParser parser, Tables databaseTables) {
this.databaseTables = databaseTables;
MySqlParser.RootContext root = parser.root();
ParseTreeWalker.DEFAULT.walk(new MySqlDdlParserListener(), root);
ProxyParseTreeListener proxyParseTreeListener = new ProxyParseTreeListener();
proxyParseTreeListener.add(new CreateTableParserListener());
proxyParseTreeListener.add(new DropTableParserListener());
proxyParseTreeListener.add(new AlterTableParserListener());
proxyParseTreeListener.add(new ColumnDefinitionParserListener());
proxyParseTreeListener.add(new ExitSqlStatementParserListener());
ParseTreeWalker.DEFAULT.walk(proxyParseTreeListener, root);
}
@Override
@ -76,23 +87,79 @@ private String parseColumnName(MySqlParser.UidContext uidContext) {
return uidContext.getText();
}
private void resolveColumnDataType(MySqlParser.DataTypeContext dataTypeContext) {
String dataTypeName;
int jdbcType = Types.NULL;
if (dataTypeContext instanceof MySqlParser.StringDataTypeContext) {
// CHAR | VARCHAR | TINYTEXT | TEXT | MEDIUMTEXT | LONGTEXT
MySqlParser.StringDataTypeContext stringDataTypeContext = (MySqlParser.StringDataTypeContext) dataTypeContext;
dataTypeName = stringDataTypeContext.typeName.getText();
if (stringDataTypeContext.lengthOneDimension() != null) {
Integer length = Integer.valueOf(stringDataTypeContext.lengthOneDimension().decimalLiteral().getText());
columnEditor.length(length);
}
} else if (dataTypeContext instanceof MySqlParser.DimensionDataTypeContext) {
// TINYINT | SMALLINT | MEDIUMINT | INT | INTEGER | BIGINT
// REAL | DOUBLE | FLOAT
// DECIMAL | NUMERIC | DEC | FIXED
// BIT | TIME | TIMESTAMP | DATETIME | BINARY | VARBINARY | YEAR
MySqlParser.DimensionDataTypeContext dimensionDataTypeContext = (MySqlParser.DimensionDataTypeContext) dataTypeContext;
dataTypeName = dimensionDataTypeContext.typeName.getText();
Integer length = null;
Integer scale = null;
if (dimensionDataTypeContext.lengthOneDimension() != null) {
length = Integer.valueOf(dimensionDataTypeContext.lengthOneDimension().decimalLiteral().getText());
}
if (dimensionDataTypeContext.lengthTwoDimension() != null) {
List<MySqlParser.DecimalLiteralContext> decimalLiterals = dimensionDataTypeContext.lengthTwoDimension().decimalLiteral();
length = Integer.valueOf(decimalLiterals.get(0).getText());
scale = Integer.valueOf(decimalLiterals.get(1).getText());
}
if (dimensionDataTypeContext.lengthTwoOptionalDimension() != null) {
List<MySqlParser.DecimalLiteralContext> decimalLiterals = dimensionDataTypeContext.lengthTwoOptionalDimension().decimalLiteral();
length = Integer.valueOf(decimalLiterals.get(0).getText());
if (decimalLiterals.size() > 1) {
scale = Integer.valueOf(decimalLiterals.get(1).getText());
}
}
if (length != null) {
columnEditor.length(length);
}
if (scale != null) {
columnEditor.scale(scale);
}
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.SimpleDataTypeContext) {
// DATE | TINYBLOB | BLOB | MEDIUMBLOB | LONGBLOB | BOOL | BOOLEAN
dataTypeName = ((MySqlParser.SimpleDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.CollectionDataTypeContext) {
// ENUM | SET
// do not care about charsetName or collationName
dataTypeName = ((MySqlParser.CollectionDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.SpatialDataTypeContext) {
// GEOMETRYCOLLECTION | LINESTRING | MULTILINESTRING | MULTIPOINT | MULTIPOLYGON | POINT | POLYGON
dataTypeName = ((MySqlParser.SpatialDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else {
throw new IllegalStateException("Not recognized instance of data type context for " + dataTypeContext.getText());
}
columnEditor.type(dataTypeName);
columnEditor.jdbcType(jdbcType);
}
/**
* Parser listener for MySQL alter table queries.
*
* @author Roman Kuchár <kucharrom@gmail.com>.
* Parser listener for MySQL create table queries.
*/
// TODO: Do we want to split one big listener into a smaller ones?
// TODO: Can be used by some proxy listener described here: https://github.com/antlr/antlr4/issues/841
private class MySqlDdlParserListener extends MySqlParserBaseListener {
private class CreateTableParserListener extends MySqlParserBaseListener {
private TableEditor tableEditor;
private ColumnEditor columnEditor;
private int parsingColumnIndex = 0;
private List<ColumnEditor> columnEditors;
/*
* START - Listening event for create table statements
*/
@Override
public void enterQueryCreateTable(MySqlParser.QueryCreateTableContext ctx) {
TableId tableId = parseQualifiedTableId(ctx.tableName());
@ -169,13 +236,13 @@ public void enterPrimaryKeyTableConstraint(MySqlParser.PrimaryKeyTableConstraint
tableEditor.setPrimaryKeyNames(pkColumnNames);
super.enterPrimaryKeyTableConstraint(ctx);
}
/*
* END - Listening event for create table statements
*/
}
/**
* Parser listener for MySQL drop table queries.
*/
private class DropTableParserListener extends MySqlParserBaseListener {
/*
* Drop table listener
*/
@Override
public void enterDropTable(MySqlParser.DropTableContext ctx) {
Interval interval = new Interval(ctx.start.getStartIndex(), ctx.tables().start.getStartIndex() - 1);
@ -188,14 +255,21 @@ public void enterDropTable(MySqlParser.DropTableContext ctx) {
});
super.enterDropTable(ctx);
}
}
/**
* Parser listener for MySQL alter table queries.
*/
private class AlterTableParserListener extends MySqlParserBaseListener {
private static final int STARTING_INDEX = 1;
private int parsingColumnIndex = STARTING_INDEX;
private List<ColumnEditor> columnEditors;
/*
* START - Listening events for alter table statements
*/
@Override
public void enterAlterTable(MySqlParser.AlterTableContext ctx) {
TableId tableId = parseQualifiedTableId(ctx.tableName());
// TODO: should be table created if it does not exists in memory model?
tableEditor = databaseTables.editOrCreateTable(tableId);
super.enterAlterTable(ctx);
}
@ -204,7 +278,6 @@ public void enterAlterTable(MySqlParser.AlterTableContext ctx) {
public void enterAlterByAddColumn(MySqlParser.AlterByAddColumnContext ctx) {
String columnName = parseColumnName(ctx.uid(0));
columnEditor = Column.editor().name(columnName);
// TODO: how can i set a column position and update other existing columns position?
if (ctx.FIRST() != null) {
//TODO: this new column should have the first position in table
} else if (ctx.AFTER() != null) {
@ -222,6 +295,7 @@ public void enterAlterByAddColumns(MySqlParser.AlterByAddColumnsContext ctx) {
String columnName = parseColumnName(uidContext);
columnEditors.add(Column.editor().name(columnName));
}
columnEditor = columnEditors.get(0);
super.enterAlterByAddColumns(ctx);
}
@ -244,19 +318,32 @@ public void exitAlterTable(MySqlParser.AlterTableContext ctx) {
debugParsed(ctx.getParent());
super.exitAlterTable(ctx);
}
/*
* END - Listening events for alter table statements
*/
/*
* START - Listening events for column definition
*/
@Override
public void enterColumnDefinition(MySqlParser.ColumnDefinitionContext ctx) {
public void exitColumnDefinition(MySqlParser.ColumnDefinitionContext ctx) {
if (columnEditors != null) {
// column editor list is not null when a multiple columns are parsed in one statement
columnEditor = columnEditors.get(parsingColumnIndex++);
if (columnEditors.size() > parsingColumnIndex) {
// assign next column editor to parse another column definition
columnEditor = columnEditors.get(parsingColumnIndex++);
} else {
// all columns parsed
// reset global variables for next parsed statement
columnEditors = null;
parsingColumnIndex = STARTING_INDEX;
}
}
super.exitColumnDefinition(ctx);
}
}
/**
* Parser listener for MySQL column definition queries.
*/
private class ColumnDefinitionParserListener extends MySqlParserBaseListener {
@Override
public void enterColumnDefinition(MySqlParser.ColumnDefinitionContext ctx) {
resolveColumnDataType(ctx.dataType());
super.enterColumnDefinition(ctx);
}
@ -288,91 +375,20 @@ public void enterAutoIncrementColumnConstraint(MySqlParser.AutoIncrementColumnCo
columnEditor.generated(true);
super.enterAutoIncrementColumnConstraint(ctx);
}
/*
* END - Listening events for column definition
*/
}
/**
* Parser listener for MySQL alter table queries.
*/
private class ExitSqlStatementParserListener extends MySqlParserBaseListener {
/*
* Last caught event for sql statement
*/
@Override
public void exitSqlStatement(MySqlParser.SqlStatementContext ctx) {
// reset global values for next statement that could be parsed with this instance
tableEditor = null;
columnEditor = null;
columnEditors = null;
parsingColumnIndex = 0;
super.exitSqlStatement(ctx);
}
private void resolveColumnDataType(MySqlParser.DataTypeContext dataTypeContext) {
String dataTypeName;
int jdbcType = Types.NULL;
if (dataTypeContext instanceof MySqlParser.StringDataTypeContext) {
// CHAR | VARCHAR | TINYTEXT | TEXT | MEDIUMTEXT | LONGTEXT
MySqlParser.StringDataTypeContext stringDataTypeContext = (MySqlParser.StringDataTypeContext) dataTypeContext;
dataTypeName = stringDataTypeContext.typeName.getText();
if (stringDataTypeContext.lengthOneDimension() != null) {
Integer length = Integer.valueOf(stringDataTypeContext.lengthOneDimension().decimalLiteral().getText());
columnEditor.length(length);
}
} else if (dataTypeContext instanceof MySqlParser.DimensionDataTypeContext) {
// TINYINT | SMALLINT | MEDIUMINT | INT | INTEGER | BIGINT
// REAL | DOUBLE | FLOAT
// DECIMAL | NUMERIC | DEC | FIXED
// BIT | TIME | TIMESTAMP | DATETIME | BINARY | VARBINARY | YEAR
MySqlParser.DimensionDataTypeContext dimensionDataTypeContext = (MySqlParser.DimensionDataTypeContext) dataTypeContext;
dataTypeName = dimensionDataTypeContext.typeName.getText();
Integer length = null;
Integer scale = null;
if (dimensionDataTypeContext.lengthOneDimension() != null) {
length = Integer.valueOf(dimensionDataTypeContext.lengthOneDimension().decimalLiteral().getText());
}
if (dimensionDataTypeContext.lengthTwoDimension() != null) {
List<MySqlParser.DecimalLiteralContext> decimalLiterals = dimensionDataTypeContext.lengthTwoDimension().decimalLiteral();
length = Integer.valueOf(decimalLiterals.get(0).getText());
scale = Integer.valueOf(decimalLiterals.get(1).getText());
}
if (dimensionDataTypeContext.lengthTwoOptionalDimension() != null) {
List<MySqlParser.DecimalLiteralContext> decimalLiterals = dimensionDataTypeContext.lengthTwoOptionalDimension().decimalLiteral();
length = Integer.valueOf(decimalLiterals.get(0).getText());
if (decimalLiterals.size() > 1) {
scale = Integer.valueOf(decimalLiterals.get(1).getText());
}
}
if (length != null) {
columnEditor.length(length);
}
if (scale != null) {
columnEditor.scale(scale);
}
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.SimpleDataTypeContext) {
// DATE | TINYBLOB | BLOB | MEDIUMBLOB | LONGBLOB | BOOL | BOOLEAN
dataTypeName = ((MySqlParser.SimpleDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.CollectionDataTypeContext) {
// ENUM | SET
// do not care about charsetName or collationName
dataTypeName = ((MySqlParser.CollectionDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else if (dataTypeContext instanceof MySqlParser.SpatialDataTypeContext) {
// GEOMETRYCOLLECTION | LINESTRING | MULTILINESTRING | MULTIPOINT | MULTIPOLYGON | POINT | POLYGON
dataTypeName = ((MySqlParser.SpatialDataTypeContext) dataTypeContext).typeName.getText();
// TODO: resolve jdbc type
} else {
throw new IllegalStateException("Not recognized instance of data type context for " + dataTypeContext.getText());
}
columnEditor.type(dataTypeName);
columnEditor.jdbcType(jdbcType);
}
}
}