Debezium在很长一段时间内都是不支持解析Mysql column default值,issues https://issues.jboss.org/browse/DBZ-191;
很开心的是,经过一段时间的尝试,给debezium mysql connector增加了解析默认值的功能,并且被merge到了master。^.^
下面具体描述如何实现
1. 获取默认值
MySqlDdlParser.class这个类主要用来解析ddl语法,它支持对default语法的解析,内部有一个处理default值的方法,只不过该方法会忽略默认值。
protected void parseDefaultClause(Marker start) {
tokens.consume("DEFAULT");
if (isNextTokenQuotedIdentifier()) {
// We know that it is a quoted literal ...
parseLiteral(start);
} else {
if (tokens.matchesAnyOf("CURRENT_TIMESTAMP", "NOW")) {
parseCurrentTimestampOrNow();
parseOnUpdateOrDelete(tokens.mark());
} else if (tokens.canConsume("NULL")) {
// do nothing ...
} else {
parseLiteral(start);
}
}
}
从这个方法入手,对其进行改造,处理默认值; 首先ColumnEditor, Column需要增加下面的2个方法
/**
* Get the default value of the column
*
* @return the default value
*/
Object defaultValue();
/**
* Determine whether this column's has a default value
*
* @return {@code true} if the default value was provided, or {@code false} otherwise
*/
boolean hasDefaultValue();
对parseDefaultClause方法改造
protected void parseDefaultClause(Marker start, ColumnEditor column) {
tokens.consume("DEFAULT");
if (isNextTokenQuotedIdentifier()) {
// We know that it is a quoted literal ...
Object defaultValue = parseLiteral(start);
column.defaultValue(defaultValue);
} else {
if (tokens.matchesAnyOf("CURRENT_TIMESTAMP", "NOW")) {
parseCurrentTimestampOrNow();
parseOnUpdateOrDelete(tokens.mark());
//CURRENT_TIMESTAMP and NOW default value will be replaced with epoch timestamp
column.defaultValue("1970-01-01 00:00:00");
} else if (tokens.canConsume("NULL")) {
// If the default value of column is Null, we will set default value null;
column.defaultValue(null);
} else {
Object defaultValue = parseLiteral(start);
column.defaultValue(defaultValue);
}
}
}
parseLiteral()方法能够解析出默认值,通过这个方法得到的默认值,设置给column.defaultValue;由于CURRENT_TIMESTAMP和NOW的特殊性,根据gunnarmorling探讨,决定将这2种默认值设置为epoch timestmap。通过parseDefaultClause方法,ddl语句的默认值都能够被解析获取。这部分工作量其实比较小。
2. avro schema 默认值
默认值最后会在TableSchemaBuilder.addField方法里设置给SchemaBuilder对象
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());
}
}
如果列有默认值,会调用fieldBuilder.defaultValue(column.defaultValue());,fieldBuilder.defaultValue方法会验证默认值的类型,如果默认值类型不合法会抛出异常。如果合法,avro schema里就有了默认值。
3. 转化默认值为合法类型
1和2这2个步骤分别为获取默认值,和最后的设置schema默认值。这2部分都是相对来讲简单,最难的部分是将1里获取的默认值类型转化为2里能够成功设置的值类型。
这步转化的步骤会在每一列column被成功解析后处理,这个处理过程在MysqlDdlParser.parseCreateColumn方法内。
protected Column parseCreateColumn(Marker start, TableEditor table, String columnName, String newColumnName) {
...
parseColumnDefinition(start, columnName, tokens, table, column, isPrimaryKey);
//增加的代码
convertDefaultValueToSchemaType(column);
// Update the table ...
Column newColumnDefn = column.create();
table.addColumns(newColumnDefn);
...
}
parseColumnDefinition方法会解析mysql ddl,也是在这个方法内,debezium获取mysql的默认值。这个方法之后转化默认值,调用convertDefaultValueToSchemaType。
private void convertDefaultValueToSchemaType(ColumnEditor columnEditor) {
final Column column = columnEditor.create();
// if converters is not null and the default value is not null, we need to convert default value
if (converters != null && columnEditor.defaultValue() != null) {
Object defaultValue = columnEditor.defaultValue();
final SchemaBuilder schemaBuilder = converters.schemaBuilder(column);
if (schemaBuilder == null) {
return;
}
final Schema schema = schemaBuilder.build();
//In order to get the valueConverter for this column, we have to create a field;
//The index value -1 in the field will never used when converting default value;
//So we can set any number here;
final Field field = new Field(column.name(), -1, schema);
final ValueConverter valueConverter = converters.converter(column, field);
if (defaultValue instanceof String) {
defaultValue = defaultValuePreConverter.convert(column, (String)defaultValue);
}
defaultValue = valueConverter.convert(defaultValue);
columnEditor.defaultValue(defaultValue);
}
}
debezium本来已经有一个类型转换器,每当mysql有数据变更时,变更的内容都会被这个转换器处理。所以在这个方法内,可以实例化一个ValueConverter,借助它来转换数据类型。但是ValueConverter的目标是实时变更内容的转化,这些内容基本都是合法的内容,然而默认值不一定都是合法的,所以对于默认值转化不能完全支持。因此必须要有一个预先处理器,将默认值转化为ValueConverter能够处理的类型,defaultValue = defaultValuePreConverter.convert(column, (String)defaultValue)。少数的类型必须修改ValueConverter实现。
MySqlDefaultValuePreConverter.convert用来预处理字符类型的默认值,将其转化为ValueConverter能够处理的值。
public Object convert(Column column, String value) {
if (value == null) {
return value;
}
switch (column.jdbcType()) {
case Types.DATE:
return convertToLocalDate(value);
case Types.TIMESTAMP:
return convertToLocalDateTime(column, value);
case Types.TIMESTAMP_WITH_TIMEZONE:
return convertToTimestamp(value);
case Types.TIME:
return convertToDuration(column, value);
case Types.BOOLEAN:
return convertToBoolean(value);
case Types.BIT:
return convertToBits(column, value);
case Types.TINYINT:
case Types.SMALLINT:
return convertToSmallInt(value);
case Types.NUMERIC:
case Types.DECIMAL:
return convertToDecimal(value);
case Types.FLOAT:
case Types.DOUBLE:
return convertToDouble(value);
case Types.BIGINT:
return convertToBigInt(value);
case Types.INTEGER:
return convertToInteger(value);
}
return value;
}
经过MySqlDefaultValuePreConverter和ValueConverter转化后的默认值是能够被SchemaBuilder接受的类型。由于debezium的实现,还是做了一些取舍。
1. mysql的DATETIME类型,会被debezium解析为timestamp类型,所以默认值必须要大于1970-01-01 00:00:00。
2. mysql timestamp默认值0000-00-00 00:00:00,会被解析为1970-01-01 00:00:00。
总结
经过这3个步骤后,debezium能够实现mysql默认值的解析了。
=v=

本文介绍如何为Debezium MySQL连接器增加解析MySQL默认值的功能,包括获取默认值、设置Avro Schema默认值及转化默认值为合法类型。

630

被折叠的 条评论
为什么被折叠?



