@Override public boolean equals(Object obj) { if (obj == this) return true; if (obj instanceof Column) { Column that = (Column) obj; return this.name().equalsIgnoreCase(that.name()) && this.typeExpression().equalsIgnoreCase(that.typeExpression()) && this.typeName().equalsIgnoreCase(that.typeName()) && this.jdbcType() == that.jdbcType() && Strings.equalsIgnoreCase(this.charsetName(),that.charsetName()) && this.position() == that.position() && this.length() == that.length() && this.scale().equals(that.scale()) && this.isOptional() == that.isOptional() && this.isAutoIncremented() == that.isAutoIncremented() && this.isGenerated() == that.isGenerated() && Objects.equals(this.defaultValue(), that.defaultValue()) && this.hasDefaultValue() == that.hasDefaultValue(); } return false; }
@Override protected ByteBuffer convertByteArray(Column column, byte[] data) { // DBZ-254 right-pad fixed-length binary column values with 0x00 (zero byte) if (column.jdbcType() == Types.BINARY && data.length < column.length()) { data = Arrays.copyOf(data, column.length()); } return super.convertByteArray(column, data); }
@Override public void alterFieldSchema(Column column, SchemaBuilder schemaBuilder) { // upper-casing type names to be consistent across connectors schemaBuilder.parameter(TYPE_NAME_PARAMETER_KEY, column.typeName().toUpperCase(Locale.ENGLISH)); if (column.length() != Column.UNSET_INT_VALUE) { schemaBuilder.parameter(TYPE_LENGTH_PARAMETER_KEY, String.valueOf(column.length())); } if (column.scale().isPresent()) { schemaBuilder.parameter(TYPE_SCALE_PARAMETER_KEY, String.valueOf(column.scale().get())); } } }
protected void assertColumn(Table table, String name, String typeName, int jdbcType, int length, String charsetName, boolean optional) { Column column = table.columnWithName(name); assertThat(column.name()).isEqualTo(name); assertThat(column.typeName()).isEqualTo(typeName); assertThat(column.jdbcType()).isEqualTo(jdbcType); assertThat(column.length()).isEqualTo(length); assertThat(column.charsetName()).isEqualTo(charsetName); assertFalse(column.scale().isPresent()); assertThat(column.isOptional()).isEqualTo(optional); assertThat(column.isGenerated()).isFalse(); assertThat(column.isAutoIncremented()).isFalse(); }
private Document toDocument(Column column) { Document document = Document.create(); document.setString("name", column.name()); document.setNumber("jdbcType", column.jdbcType()); if (column.nativeType() != Column.UNSET_INT_VALUE) { document.setNumber("nativeType", column.nativeType()); } document.setString("typeName", column.typeName()); document.setString("typeExpression", column.typeExpression()); document.setString("charsetName", column.charsetName()); if (column.length() != Column.UNSET_INT_VALUE) { document.setNumber("length", column.length()); } column.scale().ifPresent(s -> document.setNumber("scale", s)); document.setNumber("position", column.position()); document.setBoolean("optional", column.isOptional()); document.setBoolean("autoIncremented", column.isAutoIncremented()); document.setBoolean("generated", column.isGenerated()); return document; }
table.addColumn(Column.editor().name(name).create()); }); if (table.columns().isEmpty()) { selectedColumnsByAlias.forEach((columnName, fromTableColumn) -> { if (fromTableColumn != null && columnName != null) table.addColumn(fromTableColumn.edit().name(columnName).create()); }); } else { table.columns().forEach(column -> { Column selectedColumn = selectedColumnsByAlias.get(column.name()); if (selectedColumn != null) { changedColumns.add(column.edit() .jdbcType(selectedColumn.jdbcType()) .type(selectedColumn.typeName(), selectedColumn.typeExpression()) .length(selectedColumn.length()) .scale(selectedColumn.scale().orElse(null)) .autoIncremented(selectedColumn.isAutoIncremented()) .generated(selectedColumn.isGenerated()) .optional(selectedColumn.isOptional()).create()); List<String> viewPkColumnNames = new ArrayList<>(); selectedColumnsByAlias.forEach((viewColumnName, fromTableColumn) -> { if (fromTablePkColumnNames.contains(fromTableColumn.name())) { viewPkColumnNames.add(viewColumnName);
final int localType = column.nativeType(); final int incomingType = message.getType().getOid(); if (localType != incomingType) { logger.info("detected new type for column '{}', old type was {} ({}), new type is {} ({}); refreshing table schema", columnName, localType, column.typeName(), incomingType, message.getType().getName()); return true; final int localLength = column.length(); final int incomingLength = message.getTypeMetadata().getLength(); if (localLength != incomingLength) { return true; final int localScale = column.scale().get(); final int incomingScale = message.getTypeMetadata().getScale(); if (localScale != incomingScale) { return true; final boolean localOptional = column.isOptional(); final boolean incomingOptional = message.isOptional(); if (localOptional != incomingOptional) {
@Override public SchemaBuilder schemaBuilder(Column column) { switch (column.jdbcType()) { // Numeric integers case Types.TINYINT: // values are an 8-bit unsigned integer value between 0 and 255, we thus need to store it in short int return SchemaBuilder.int16(); // Floating point case microsoft.sql.Types.SMALLMONEY: case microsoft.sql.Types.MONEY: return SpecialValueDecimal.builder(decimalMode, column.length(), column.scale().get()); case microsoft.sql.Types.DATETIMEOFFSET: return ZonedTimestamp.builder(); default: return super.schemaBuilder(column); } }
private ValueConverter createArrayConverter(Column column, Field fieldDefn) { PostgresType arrayType = typeRegistry.get(column.nativeType()); PostgresType elementType = arrayType.getElementType(); final String elementTypeName = elementType.getName(); final String elementColumnName = column.name() + "-element"; final Column elementColumn = Column.editor() .name(elementColumnName) .jdbcType(elementType.getJdbcId()) .nativeType(elementType.getOid()) .type(elementTypeName) .optional(true) .scale(column.scale().orElse(null)) .length(column.length()) .create(); Schema elementSchema = schemaBuilder(elementColumn) .optional() .build(); final Field elementField = new Field(elementColumnName, 0, elementSchema); final ValueConverter elementConverter = converter(elementColumn, elementField); return data -> convertArray(column, fieldDefn, elementConverter, data); }
Column column = columns.get(i); if (filter != null && !filter.test(new ColumnId(tableId, column.name()))) { continue; ValueConverter converter = createValueConverterFor(column, schema.field(column.name())); converter = wrapInMappingConverterIfNeeded(mappers, tableId, column, converter); LOGGER.warn( "No converter found for column {}.{} of type {}. The column will not be part of change events for that table.", tableId, column.name(), column.typeName());
/** * Add to the supplied {@link SchemaBuilder} a field for the column with the given information. * * @param builder the schema builder; never null * @param column the column definition * @param mapper the mapping function for the column; may be null if the columns is not to be mapped to different values */ protected void addField(SchemaBuilder builder, Column column, ColumnMapper mapper) { SchemaBuilder fieldBuilder = valueConverterProvider.schemaBuilder(column); if (fieldBuilder != null) { if (mapper != null) { // Let the mapper add properties to the schema ... mapper.alterFieldSchema(column, fieldBuilder); } if (column.isOptional()) fieldBuilder.optional(); // if the default value is provided if (column.hasDefaultValue()) { fieldBuilder.defaultValue(column.defaultValue()); } builder.field(column.name(), fieldBuilder.build()); if (LOGGER.isDebugEnabled()) { LOGGER.debug("- field '{}' ({}{}) from column {}", column.name(), builder.isOptional() ? "OPTIONAL " : "", fieldBuilder.type(), column); } } else { LOGGER.warn("Unexpected JDBC type '{}' for column '{}' that will be ignored", column.jdbcType(), column.name()); } }
protected Column parseCreateColumn(Marker start, TableEditor table, String columnName, String newColumnName) { // Obtain the column editor ... Column existingColumn = table.columnWithName(columnName); ColumnEditor column = existingColumn != null ? existingColumn.edit() : Column.editor().name(columnName); AtomicBoolean isPrimaryKey = new AtomicBoolean(false); parseColumnDefinition(start, columnName, tokens, table, column, isPrimaryKey); convertDefaultValueToSchemaType(column); // Update the table ... Column newColumnDefn = column.create(); table.addColumns(newColumnDefn); if (isPrimaryKey.get()) { table.setPrimaryKeyNames(newColumnDefn.name()); } if (newColumnName != null && !newColumnName.equalsIgnoreCase(columnName)) { table.renameColumn(columnName, newColumnName); columnName = newColumnName; } // ALTER TABLE allows reordering the columns after the definition ... if (tokens.canConsume("FIRST")) { table.reorderColumn(columnName, null); } else if (tokens.canConsume("AFTER")) { table.reorderColumn(columnName, tokens.consume()); } return table.columnWithName(newColumnDefn.name()); }
protected void add(Column defn) { if (defn != null) { Column existing = columnWithName(defn.name()); int position = existing != null ? existing.position() : sortedColumns.size() + 1; sortedColumns.put(defn.name().toLowerCase(), defn.edit().position(position).create()); } assert positionsAreValid(); }
@Before public void beforeEach() { editor = Column.editor(); column = null; }
/** * Converts a string object for an object type of {@link LocalDateTime}. * If the column definition allows null and default value is 0000-00-00 00:00:00, we need return null, * else 0000-00-00 00:00:00 will be replaced with 1970-01-01 00:00:00; * * @param column the column definition describing the {@code data} value; never null * @param value the string object to be converted into a {@link LocalDateTime} type; * @return the converted value; */ private Object convertToLocalDateTime(Column column, String value) { final boolean matches = EPOCH_EQUIVALENT_TIMESTAMP.matcher(value).matches() || "0".equals(value); if (matches) { if (column.isOptional()) { return null; } value = EPOCH_TIMESTAMP; } return LocalDateTime.from(timestampFormat(column.length()).parse(value)); }
@Test public void parseUnsignedBigIntDefaultValueToLong() { String sql = "CREATE TABLE UNSIGNED_BIGINT_TABLE (\n" + " A BIGINT UNSIGNED NULL DEFAULT 0,\n" + " B BIGINT UNSIGNED NULL DEFAULT '10',\n" + " C BIGINT UNSIGNED NULL,\n" + " D BIGINT UNSIGNED NOT NULL,\n" + " E BIGINT UNSIGNED NOT NULL DEFAULT 0,\n" + " F BIGINT UNSIGNED NOT NULL DEFAULT '0'\n" + ");"; parser.parse(sql, tables); Table table = tables.forTable(new TableId(null, null, "UNSIGNED_BIGINT_TABLE")); assertThat(table.columnWithName("A").defaultValue()).isEqualTo(0L); assertThat(table.columnWithName("B").defaultValue()).isEqualTo(10L); assertThat(table.columnWithName("C").isOptional()).isEqualTo(true); assertThat(table.columnWithName("C").hasDefaultValue()).isTrue(); assertThat(table.columnWithName("D").isOptional()).isEqualTo(false); assertThat(table.columnWithName("D").hasDefaultValue()).isFalse(); assertThat(table.columnWithName("E").isOptional()).isEqualTo(false); assertThat(table.columnWithName("E").defaultValue()).isEqualTo(0L); assertThat(table.columnWithName("F").defaultValue()).isEqualTo(0L); }
/** * Determine whether this column has a {@link #typeName()} or {@link #jdbcType()} to which a character set applies. * * @return {@code true} if a character set applies the column's type, or {@code false} otherwise */ default boolean typeUsesCharset() { switch (jdbcType()) { case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: case Types.CLOB: case Types.NCHAR: case Types.NVARCHAR: case Types.LONGNVARCHAR: case Types.NCLOB: case Types.DATALINK: case Types.SQLXML: return true; default: return false; } } }
protected TableImpl(TableId id, List<Column> sortedColumns, List<String> pkColumnNames, String defaultCharsetName) { this.id = id; this.columnDefs = Collections.unmodifiableList(sortedColumns); this.pkColumnNames = pkColumnNames == null ? Collections.emptyList() : Collections.unmodifiableList(pkColumnNames); Map<String, Column> defsByLowercaseName = new LinkedHashMap<>(); for (Column def : this.columnDefs) { defsByLowercaseName.put(def.name().toLowerCase(), def); } this.columnsByLowercaseName = Collections.unmodifiableMap(defsByLowercaseName); this.defaultCharsetName = defaultCharsetName; }