/** * Obtain an editor for the table with the given ID. This method does not lock or modify the set of table definitions, so use * with caution. The resulting editor can be used to modify the table definition, but when completed the new {@link Table} * needs to be added back to this object via {@link #overwriteTable(Table)}. * * @param tableId the identifier of the table * @return the editor for the table, or null if there is no table with the specified ID */ public TableEditor editOrCreateTable(TableId tableId) { Table table = forTable(tableId); return table == null ? Table.editor().tableId(tableId) : table.edit(); }
public Table getTableSchemaFromChangeTable(ChangeTable changeTable) throws SQLException { final DatabaseMetaData metadata = connection().getMetaData(); final TableId changeTableId = changeTable.getChangeTableId(); List<ColumnEditor> columnEditors = new ArrayList<>(); try (ResultSet rs = metadata.getColumns(realDatabaseName, changeTableId.schema(), changeTableId.table(), null)) { while (rs.next()) { readTableColumn(rs, changeTableId, null).ifPresent(columnEditors::add); } } // The first 5 columns and the last column of the change table are CDC metadata final List<Column> columns = columnEditors.subList(CHANGE_TABLE_DATA_COLUMN_OFFSET, columnEditors.size() - 1).stream() .map(c -> c.position(c.position() - CHANGE_TABLE_DATA_COLUMN_OFFSET).create()) .collect(Collectors.toList()); final List<String> pkColumnNames = new ArrayList<>(); prepareQuery(GET_LIST_OF_KEY_COLUMNS, ps -> ps.setInt(1, changeTable.getChangeTableObjectId()), rs -> { while (rs.next()) { pkColumnNames.add(rs.getString(2)); } }); Collections.sort(columns); return Table.editor() .tableId(changeTable.getSourceTableId()) .addColumns(columns) .setPrimaryKeyNames(pkColumnNames) .create(); }
@Override public void exitSubqueryTableItem(MySqlParser.SubqueryTableItemContext ctx) { parser.runIfNotNull(() -> { // parsing subselect String tableAlias = parser.parseName(ctx.uid()); TableId aliasTableId = parser.resolveTableId(parser.currentSchema(), tableAlias); selectTableEditor.tableId(aliasTableId); tableByAlias.put(aliasTableId, selectTableEditor.create()); }, tableEditor); super.exitSubqueryTableItem(ctx); }
@Override public void exitAlterTable(MySqlParser.AlterTableContext ctx) { parser.runIfNotNull(() -> { listeners.remove(columnDefinitionListener); parser.databaseTables().overwriteTable(tableEditor.create()); parser.signalAlterTable(tableEditor.tableId(), null, ctx.getParent()); }, tableEditor); super.exitAlterTable(ctx); }
public Table getTableSchemaFromTable(ChangeTable changeTable) throws SQLException { final DatabaseMetaData metadata = connection().getMetaData(); List<Column> columns = new ArrayList<>(); try (ResultSet rs = metadata.getColumns( realDatabaseName, changeTable.getSourceTableId().schema(), changeTable.getSourceTableId().table(), null) ) { while (rs.next()) { readTableColumn(rs, changeTable.getSourceTableId(), null).ifPresent(ce -> columns.add(ce.create())); } } final List<String> pkColumnNames = readPrimaryKeyNames(metadata, changeTable.getSourceTableId()); Collections.sort(columns); return Table.editor() .tableId(changeTable.getSourceTableId()) .addColumns(columns) .setPrimaryKeyNames(pkColumnNames) .create(); }
@Override public void exitColumnCreateTable(MySqlParser.ColumnCreateTableContext ctx) { parser.runIfNotNull(() -> { // Make sure that the table's character set has been set ... if (!tableEditor.hasDefaultCharsetName()) { tableEditor.setDefaultCharsetName(parser.currentDatabaseCharset()); } listeners.remove(columnDefinitionListener); columnDefinitionListener = null; // remove column definition parser listener parser.databaseTables().overwriteTable(tableEditor.create()); parser.signalCreateTable(tableEditor.tableId(), ctx); }, tableEditor); super.exitColumnCreateTable(ctx); }
@Override public void enterAlterByModifyColumn(MySqlParser.AlterByModifyColumnContext ctx) { parser.runIfNotNull(() -> { String columnName = parser.parseName(ctx.uid(0)); Column column = tableEditor.columnWithName(columnName); if (column != null) { columnDefinitionListener = new ColumnDefinitionParserListener(tableEditor, column.edit(), parser.dataTypeResolver(), parser.getConverters()); listeners.add(columnDefinitionListener); } else { throw new ParsingException(null, "Trying to change column " + columnName + " in " + tableEditor.tableId().toString() + " table, which does not exist. Query: " + getText(ctx)); } }, tableEditor); super.enterAlterByModifyColumn(ctx); }
@Override public void enterAlterByRenameColumn(MySqlParser.AlterByRenameColumnContext ctx) { parser.runIfNotNull(() -> { String oldColumnName = parser.parseName(ctx.oldColumn); Column existingColumn = tableEditor.columnWithName(oldColumnName); if (existingColumn != null) { // DBZ-771 unset previously set default value, as it's not kept by MySQL; for any column modifications a new // default value (which could be the same) has to be provided by the column_definition which we'll parse later // on; only in 8.0 (not yet supported by this parser) columns can be renamed without repeating the full column // definition; so in fact it's arguably not correct to use edit() on the existing column to begin with, but // I'm going to leave this as is for now, to be prepared for the ability of updating column definitions in 8.0 ColumnEditor columnEditor = existingColumn.edit(); // columnEditor.unsetDefaultValue(); columnDefinitionListener = new ColumnDefinitionParserListener(tableEditor, columnEditor, parser.dataTypeResolver(), parser.getConverters()); listeners.add(columnDefinitionListener); } else { throw new ParsingException(null, "Trying to change column " + oldColumnName + " in " + tableEditor.tableId().toString() + " table, which does not exist. Query: " + getText(ctx)); } }, tableEditor); super.enterAlterByRenameColumn(ctx); }
@Override public void enterAlterByRename(MySqlParser.AlterByRenameContext ctx) { parser.runIfNotNull(() -> { final TableId newTableId = ctx.uid() != null ? parser.resolveTableId(parser.currentSchema(), parser.parseName(ctx.uid())) : parser.parseQualifiedTableId(ctx.fullId()); parser.databaseTables().renameTable(tableEditor.tableId(), newTableId); // databaseTables are updated clear table editor so exitAlterTable will not update a table by table editor tableEditor = null; }, tableEditor); super.enterAlterByRename(ctx); }
@Override public void enterAlterByChangeColumn(MySqlParser.AlterByChangeColumnContext ctx) { parser.runIfNotNull(() -> { String oldColumnName = parser.parseName(ctx.oldColumn); Column existingColumn = tableEditor.columnWithName(oldColumnName); if (existingColumn != null) { // DBZ-771 unset previously set default value, as it's not kept by MySQL; for any column modifications a new // default value (which could be the same) has to be provided by the column_definition which we'll parse later // on; only in 8.0 (not yet supported by this parser) columns can be renamed without repeating the full column // definition; so in fact it's arguably not correct to use edit() on the existing column to begin with, but // I'm going to leave this as is for now, to be prepared for the ability of updating column definitions in 8.0 ColumnEditor columnEditor = existingColumn.edit(); columnEditor.unsetDefaultValue(); columnDefinitionListener = new ColumnDefinitionParserListener(tableEditor, columnEditor, parser.dataTypeResolver(), parser.getConverters()); listeners.add(columnDefinitionListener); } else { throw new ParsingException(null, "Trying to change column " + oldColumnName + " in " + tableEditor.tableId().toString() + " table, which does not exist. Query: " + getText(ctx)); } }, tableEditor); super.enterAlterByChangeColumn(ctx); }
@Test public void shouldCreateTableWhenEditorHasIdButNoColumns() { table = editor.tableId(id).create(); assertThat(table.columnWithName("any")).isNull(); assertThat(table.columns()).isEmpty(); assertThat(table.primaryKeyColumnNames()).isEmpty(); }
@Test public void shouldFindNonExistingColumnByNameIndependentOfCase() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).position(1).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).position(1).create(); editor.addColumns(c1, c2, c3); editor.columns().forEach(col -> { assertThat(editor.columnWithName(col.name())).isNotNull(); assertThat(editor.columnWithName(col.name().toUpperCase())).isNotNull(); assertThat(editor.columnWithName(col.name().toLowerCase())).isNotNull(); }); assertThat(editor.columnWithName("WOOPS")).isNull(); }
@Test(expected = IllegalArgumentException.class) public void shouldNotAllowAddingPrimaryKeyColumnWhenNotFound() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).position(1).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).position(1).create(); editor.addColumns(c1, c2, c3); editor.setPrimaryKeyNames("C1", "WOOPS"); }
@Test(expected = IllegalArgumentException.class) public void shouldNotReorderColumnIfNameDoesNotMatch() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).autoIncremented(true).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).autoIncremented(true).create(); editor.addColumns(c1, c2, c3); editor.reorderColumn("WOOPS", "C2"); assertValidPositions(editor); }
@Test public void shouldFindGeneratedColumns() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).generated(true).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).generated(true).create(); editor.addColumns(c1, c2, c3); editor.setPrimaryKeyNames("C1"); table = editor.create(); assertThat(table.retrieveColumnNames()).containsExactly("C1", "C2", "C3"); table.columns().forEach(col -> { assertThat(table.isGenerated(col.name())).isEqualTo(col.isGenerated()); }); assertValidPositions(editor); }
@Test public void shouldFindAutoIncrementedColumns() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).autoIncremented(true).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).autoIncremented(true).create(); editor.addColumns(c1, c2, c3); editor.setPrimaryKeyNames("C1"); table = editor.create(); assertThat(table.retrieveColumnNames()).containsExactly("C1", "C2", "C3"); table.columns().forEach(col -> { assertThat(table.isAutoIncremented(col.name())).isEqualTo(col.isAutoIncremented()); }); assertValidPositions(editor); }
@Test public void shouldRemoveColumnByName() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).autoIncremented(true).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).autoIncremented(true).create(); editor.addColumns(c1, c2, c3); editor.removeColumn("C2"); assertThat(editor.columns()).containsExactly(editor.columnWithName("C1"), editor.columnWithName("C3")); assertValidPositions(editor); }
@Before public void beforeEach() { table = Table.editor() .tableId(id) .addColumns(Column.editor().name("C1") .type("VARCHAR").jdbcType(Types.VARCHAR).length(10) .generated(true) .optional(false) .create(), Column.editor().name("C2") .type("NUMBER").jdbcType(Types.NUMERIC).length(5) .optional(false) .create(), Column.editor().name("C3") .type("DATE").jdbcType(Types.DATE).length(4) .optional(true) .create(), Column.editor().name("C4") .type("COUNTER").jdbcType(Types.INTEGER) .autoIncremented(true) .optional(true) .create()) .setPrimaryKeyNames("C1", "C2") .create(); c1 = table.columnWithName("C1"); c2 = table.columnWithName("C2"); c3 = table.columnWithName("C3"); c4 = table.columnWithName("C4"); }
@Test public void shouldAllowAddingPrimaryKeyColumnWhenFound() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).position(1).create(); Column c3 = columnEditor.name("C3").type("DATE").jdbcType(Types.DATE).position(1).create(); editor.addColumns(c1, c2, c3); editor.setPrimaryKeyNames("C1"); c1 = editor.columnWithName(c1.name()); c2 = editor.columnWithName(c2.name()); c3 = editor.columnWithName(c3.name()); assertThat(c1.position()).isEqualTo(1); assertThat(c2.position()).isEqualTo(2); assertThat(c3.position()).isEqualTo(3); table = editor.create(); assertThat(table.retrieveColumnNames()).containsExactly("C1", "C2", "C3"); assertThat(table.columns()).containsExactly(c1, c2, c3); assertThat(table.primaryKeyColumnNames()).containsOnly("C1"); assertValidPositions(editor); }
@Test public void shouldReorderColumns() { editor.tableId(id); Column c1 = columnEditor.name("C1").type("VARCHAR").jdbcType(Types.VARCHAR).length(10).position(1).create(); Column c2 = columnEditor.name("C2").type("NUMBER").jdbcType(Types.NUMERIC).length(5).autoIncremented(true).create();