How to transform all timestamp fields according to avro scheme when using Kafka Connect? - apache-kafka

In our database we have over 20 fields which we need to transform from long to timestamp. Why there is no generic solution to transfer all this value ?
I know I can define:
"transforms":"tsFormat",
"transforms.tsFormat.type": "org.apache.kafka.connect.transforms.TimestampConverter$Value",
"transforms.tsFormat.target.type": "string",
"transforms.tsFormat.field": "ts_col1",
"transforms.tsFormat.field": "ts_col2",
but this is not solution for us. When we add new timestamp to db we need to update connector too
is there some generic solution to transform all fields according to avro schema ?
We are using debezium which for all timestamp fields create something like this:
{
"name": "PLATNOST_DO",
"type": {
"type": "long",
"connect.version": 1,
"connect.name": "io.debezium.time.Timestamp"
}
},
so how to find all type with connect.name = 'io.debezium.time.Timestamp' and transform it to timestamp

You'd need to write your own transform to be able to dynamically iterate of the record schema, check the types, and do the conversion.
Thus why they are called simple message transforms.
Alternatively, maybe take a closer look at the Debezium properties to see if there is a missing setting that alters how timestamps get produced.

Related

Using Kafka TimestampConverter with microseconds?

I'm new to Kafka/Avro. My downstream database (Postgres) has a timestamptz column. My upstream database (Materialize) produces the following Avro schema for the column:
"type": [
"null",
{
"logicalType": "timestamp-micros",
"type": "long"
}
]
This seems consistent with timestamptz, which also stores microseconds. However, I'm getting the following:
org.postgresql.util.PSQLException: ERROR: column "time" is of type timestamp with time zone but expression is of type bigint
Hint: You will need to rewrite or cast the expression.
Position: 207
at io.confluent.connect.jdbc.sink.JdbcSinkTask.put(JdbcSinkTask.java:122)
at org.apache.kafka.connect.runtime.WorkerSinkTask.deliverMessages(WorkerSinkTask.java:581)
... 10 more
It seems like the Kafka messages contain bigint/long and JdbcSinkConnector doesn't know how to convert it to a timestamp. I tried using Kafka's TimestampConverter, but it assumes the input timestamp is in milliseconds.
It doesn't look like TimestampConverter supports microseconds directly (https://issues.apache.org/jira/browse/KAFKA-10561)
Is there a way to convert microseconds to milliseconds in Kafka connectors? This can just be a hack, so I don't need to change the Avro schema. If there's a transform to divide by 1000 or remove the last 3 digits, that should work. Is there a way to do this without a custom transform or ksqlDB?

How to transform and extract fields in Kafka sink JDBC connector

I am using a 3rd party CDC tool that replicates data from a source database into Kafka topics. An example row is shown below:
{
"data":{
"USER_ID":{
"string":"1"
},
"USER_CATEGORY":{
"string":"A"
}
},
"beforeData":{
"Data":{
"USER_ID":{
"string":"1"
},
"USER_CATEGORY":{
"string":"B"
}
}
},
"headers":{
"operation":"UPDATE",
"timestamp":"2018-05-03T13:53:43.000"
}
}
What configuration is needed in the sink file in order to extract all the (sub)fields under data and headers and ignore those under beforeData so that the target table in which the data will be transferred by Kafka Sink will contain the following fields:
USER_ID, USER_CATEGORY, operation, timestamp
I went through the transformation list in confluent's docs but I was not able to find how to use them in order to achieve the aforementioned target.
I think you want ExtractField, and unfortunately, it's a Map.get operation, so that means 1) nested fields cannot be gotten in one pass 2) multiple fields need multiple transforms.
That being said, you might to attempt this (untested)
transforms=ExtractData,ExtractHeaders
transforms.ExtractData.type=org.apache.kafka.connect.transforms.ExtractField$Value
transforms.ExtractData.field=data
transforms.ExtractHeaders.type=org.apache.kafka.connect.transforms.ExtractField$Value
transforms.ExtractHeaders.field=headers
If that doesn't work, you might be better off implementing your own Transformations package that can at least drop values from the Struct / Map.
If you're willing to list specific field names, you can solve this by:
Using a Flatten transform to collapse the nesting (which will convert the original structure's paths into dot-delimited names)
Using a Replace transform with rename to make the field names be what you want the sink to emit
Using another Replace transform with whitelist to limit the emitted fields to those you select
For your case it might look like:
"transforms": "t1,t2,t3",
"transforms.t1.type": "org.apache.kafka.connect.transforms.Flatten$Value",
"transforms.t2.type": "org.apache.kafka.connect.transforms.ReplaceField$Value",
"transforms.t2.renames": "data.USER_ID:USER_ID,data.USER_CATEGORY:USER_CATEGORY,headers.operation:operation,headers.timestamp:timestamp",
"transforms.t3.type": "org.apache.kafka.connect.transforms.ReplaceField$Value",
"transforms.t3.whitelist": "USER_ID,USER_CATEGORY,operation,timestamp",

loopback number precision

I'm using postgresql with loopback. I have a field in a model called amount being used as a Number. Now midway through the project, a new requirement came to hold amount value with precision. But I won't be able to store amount for example. as 1.50, as it is converted and stored as 1.5, which happens with javascript number type values.
Other approach for holding precision is to converting the property from number to string. But this change will be a major code change throughout all the modules.
Also there is a property called precision and scale for PostgreSQL connector. Is there any way in the model declaration (.json file) itself to hold precision with type number? Just wanted to be sure if there is any other approach before going through this change. Any help will be highly appreciated.
You can these fields to the model.json. I have not tested the code but i think this will work
"amount": {
"type": "Number",
"required": true,
"postgresql": {
"dataType": "numeric",
"dataPrecision": 20,
"dataScale": 0
}
}

Could not make an Avro Schema object from date

I have an Avro schema with this property:
{"name": "whenDate", "type": ["date", "null"]}
I am using a Python client and the producer
confluent_kafka.avro.AvroProducer
When I load the Avro schema with aforementioned property,
I trigger this error:
not a valid Avro schema: Type property "[u'date', u'null']" not a valid
Avro schema: Union item must be a valid Avro schema: Could not make an Avro Schema object from date.
I presume this error speaks to limitations in the Confluent parser for Avro schema. That is, date is a logical type specified here, but it not as yet supported by the Confluent parser.
Can anyone confirm? More generally, should I assume that the Confluent parser does not handle the logical types for Avro schema? Thanks.
It is a logical type, I think you just need to declare it as a one.
{"name": "date", "type": "int", "logicalType": "date"}

using different avro schema for new columns

I am using flume + kafka to sink the log data to hdfs. My sink data type is Avro. In avro schema (.avsc), there is 80 fields as columns.
So I created an external table like that
CREATE external TABLE pgar.tiz_biaws_fraud
PARTITIONED BY(partition_date INT)
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.avro.AvroSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.avro.AvroContainerOutputFormat'
LOCATION '/data/datapool/flume/biaws/fraud'
TBLPROPERTIES ('avro.schema.url'='hdfs://xxxx-ns/data/datapool/flume/biaws/fraud.avsc')
Now, I need to add 25 more columns to avro schema. In that case,
if I create a new table with new schema which has 105 columns, I will have two table for one project. And if I add or remove some columns in coming days, I have to create a new table for that. I am afraid of having a lot of table which use different schema for same project.
If I swap the old schema with new schema in current table, I will have only one table for one project but I can't read and get old data anymore because of schema conflict.
What is the best way to use avro schema in case like that?
This is indeed challenging. The best way is to make sure all schema changes you make are compatible with the old data - so only remove columns with defaults, and make sure you give defaults in the columns you are adding. This way you can safely swap out the schemas without a conflict and keep reading old data. Avro is pretty clever about that, it's called "schema evolution" (in case you want to google a bit more) and allows reader and writer schemas to be a bit different.
As an aside, I want to mention that Kafka has a native HDFS connector (i.e. without Flume) that uses Confluent's schema registry to handle these kinds of schema changes automatically - you can use the registry to check if the schemas are compatible, and if they are - simply write data using the new schema and the Hive table will automatically evolve to match.
I added new columns to avro schema like that
{"name":"newColumn1", "type": "string", "default": ""},
{"name":"newColumn2", "type": "string", "default": ""},
{"name":"newColumn3", "type": "string", "default": ""},
When I use default property, if that columns doesn't exist in current data it returns default value, if that columns does exist in current data it returns the data value as expected.
For setting null value as default, you need that
{ "name": "newColumn4", "type": [ "string", "null" ], "default": "null" },
or
{ "name": "newColumn5", "type": [ "null", "string" ]},
The position of null in type property, can be first place or can be second place with default property.