This is an automated email from the ASF dual-hosted git repository.
dockerzhang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/inlong.git
The following commit(s) were added to refs/heads/master by this push:
new 4da7fc3230 [INLONG-7900][Sort] Support partition by custom fields when
upsert single table of Kafka (#8209)
4da7fc3230 is described below
commit 4da7fc32309491041c6ce2bf044f212a8be7e901
Author: Yizhou Yang <[email protected]>
AuthorDate: Wed Aug 2 18:39:51 2023 +0800
[INLONG-7900][Sort] Support partition by custom fields when upsert single
table of Kafka (#8209)
Co-authored-by: Yizhou Yang <[email protected]>
---
.../kafka/DynamicKafkaSerializationSchema.java | 16 ++-
.../SingleTableCustomFieldsPartitioner.java | 150 +++++++++++++++++++++
.../sort/kafka/table/KafkaDynamicTableFactory.java | 33 ++++-
.../table/UpsertKafkaDynamicTableFactory.java | 42 +++++-
.../SingleTableCustomFieldsPartitionerTest.java | 80 +++++++++++
5 files changed, 308 insertions(+), 13 deletions(-)
diff --git
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/DynamicKafkaSerializationSchema.java
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/DynamicKafkaSerializationSchema.java
index d80cc9d047..0413bb4cbb 100644
---
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/DynamicKafkaSerializationSchema.java
+++
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/DynamicKafkaSerializationSchema.java
@@ -93,10 +93,10 @@ class DynamicKafkaSerializationSchema implements
KafkaSerializationSchema<RowDat
*/
private final int[] metadataPositions;
private final String sinkMultipleFormat;
- private boolean multipleSink;
- private JsonDynamicSchemaFormat jsonDynamicSchemaFormat;
private final DirtyOptions dirtyOptions;
private final @Nullable DirtySink<Object> dirtySink;
+ private boolean multipleSink;
+ private JsonDynamicSchemaFormat jsonDynamicSchemaFormat;
private int[] partitions;
private int parallelInstanceId;
@@ -141,10 +141,6 @@ class DynamicKafkaSerializationSchema implements
KafkaSerializationSchema<RowDat
this.policyMap = policyMap;
}
- public void setMetricData(SinkTopicMetricData metricData) {
- this.metricData = metricData;
- }
-
static RowData createProjectedRow(
RowData consumedRow, RowKind kind, RowData.FieldGetter[]
fieldGetters) {
final int arity = fieldGetters.length;
@@ -155,6 +151,10 @@ class DynamicKafkaSerializationSchema implements
KafkaSerializationSchema<RowDat
return genericRowData;
}
+ public void setMetricData(SinkTopicMetricData metricData) {
+ this.metricData = metricData;
+ }
+
@Override
public void open(SerializationSchema.InitializationContext context) throws
Exception {
if (keySerialization != null) {
@@ -174,6 +174,10 @@ class DynamicKafkaSerializationSchema implements
KafkaSerializationSchema<RowDat
jsonDynamicSchemaFormat =
(JsonDynamicSchemaFormat)
DynamicSchemaFormatFactory.getFormat(sinkMultipleFormat);
}
+
+ if (partitioner instanceof SingleTableCustomFieldsPartitioner) {
+ ((SingleTableCustomFieldsPartitioner<?>)
partitioner).setValueFieldGetters(valueFieldGetters);
+ }
}
@Override
diff --git
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitioner.java
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitioner.java
new file mode 100644
index 0000000000..19c5ae9382
--- /dev/null
+++
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitioner.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.inlong.sort.kafka;
+
+import
org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartitioner;
+import org.apache.flink.table.api.TableSchema;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.data.RowData.FieldGetter;
+import org.apache.flink.util.Preconditions;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * The PrimaryKey Partitioner is used to extract primary key from single table
messages
+ *
+ * @param <T>
+ */
+public class SingleTableCustomFieldsPartitioner<T> extends
FlinkKafkaPartitioner<T> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(SingleTableCustomFieldsPartitioner.class);
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * schema entry names which will be used to extract fields from schema for
hashing.
+ */
+ private String partitionKey;
+
+ /**
+ * If a partitionNumber is specified, then use this number directly
instead.
+ */
+ private int partitionNumber = -1;
+
+ /**
+ * the fieldnames of the schema
+ */
+ private String[] fieldNames;
+
+ /**
+ * the value field getters used to extract rowdata
+ */
+ private FieldGetter[] valueFieldGetters;
+
+ @Override
+ public void open(int parallelInstanceId, int parallelInstances) {
+ super.open(parallelInstanceId, parallelInstances);
+ }
+
+ /**
+ * precondition: record is of rowData Type, schema and partitionKey are
not null
+ */
+ @Override
+ public int partition(T record, byte[] key, byte[] value, String
targetTopic, int[] partitions) {
+ Preconditions.checkArgument(
+ partitions != null && partitions.length > 0,
+ "Partitions of the target topic is empty.");
+ // parse the partition key fields
+ return getPartition((RowData) record, partitions);
+ }
+
+ public void setPartitionKey(String partitionKey) {
+ this.partitionKey = partitionKey;
+ }
+
+ /**
+ * input: rowdata, partitions
+ * output: the hashed partition number
+ */
+ private int getPartition(RowData data, int[] partitions) {
+ if (partitionNumber >= 0) {
+ return partitionNumber;
+ }
+ List<Integer> pos = getFieldPos();
+ // parse out the List<String> from partitionkey and then get list of
positions in schema.
+ long hashCode = 0;
+ for (int i : pos) {
+ Object fieldValue = valueFieldGetters[i].getFieldOrNull(data);
+ if (fieldValue != null) {
+ hashCode += fieldValue.hashCode();
+ }
+ }
+ return partitions[((int) ((hashCode & Integer.MAX_VALUE) %
partitions.length))];
+ }
+
+ /**
+ * output: integer list of partitionKeys' corresponding positions within
schema
+ */
+ private List<Integer> getFieldPos() {
+ // the positions of the partition keys.
+ List<Integer> positions = new ArrayList<>();
+ if (partitionKey == null) {
+ LOG.error("primaryKeyPartioner:failed to fetch partitionKey");
+ return positions;
+ }
+ // a map storing field names and their position in the schema
+ HashMap<String, Integer> map = new HashMap<>();
+ String[] keys = partitionKey.split(",");
+ for (int i = 0; i < fieldNames.length; i++) {
+ map.put(fieldNames[i], i);
+ }
+ for (String key : keys) {
+ positions.add(map.get(key));
+ }
+ return positions;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void setSchema(TableSchema schema) {
+ this.fieldNames = schema.getFieldNames();
+ }
+
+ public void setPartitionNumber(String partitionNumber) {
+ // partition Number is optional parameter and not always needed, so it
can be null.
+ if (partitionNumber != null) {
+ this.partitionNumber = Integer.parseInt(partitionNumber);
+ }
+ }
+
+ public void setValueFieldGetters(FieldGetter[] fieldGetters) {
+ this.valueFieldGetters = fieldGetters;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof SingleTableCustomFieldsPartitioner;
+ }
+
+ @Override
+ public int hashCode() {
+ return SingleTableCustomFieldsPartitioner.class.hashCode();
+ }
+}
diff --git
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/KafkaDynamicTableFactory.java
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/KafkaDynamicTableFactory.java
index bf8b8225ed..3340298305 100644
---
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/KafkaDynamicTableFactory.java
+++
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/KafkaDynamicTableFactory.java
@@ -22,6 +22,7 @@ import org.apache.inlong.sort.base.dirty.sink.DirtySink;
import org.apache.inlong.sort.base.dirty.utils.DirtySinkFactoryUtils;
import org.apache.inlong.sort.base.format.DynamicSchemaFormatFactory;
import org.apache.inlong.sort.kafka.KafkaDynamicSink;
+import org.apache.inlong.sort.kafka.SingleTableCustomFieldsPartitioner;
import
org.apache.inlong.sort.kafka.partitioner.InLongFixedPartitionPartitioner;
import org.apache.inlong.sort.kafka.partitioner.RawDataHashPartitioner;
import org.apache.inlong.sort.protocol.enums.SchemaChangePolicy;
@@ -43,6 +44,7 @@ import
org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartiti
import org.apache.flink.streaming.connectors.kafka.table.KafkaOptions;
import org.apache.flink.streaming.connectors.kafka.table.KafkaSinkSemantic;
import org.apache.flink.streaming.connectors.kafka.table.SinkBufferFlushMode;
+import org.apache.flink.table.api.TableSchema;
import org.apache.flink.table.api.ValidationException;
import org.apache.flink.table.catalog.CatalogTable;
import org.apache.flink.table.catalog.ObjectIdentifier;
@@ -108,6 +110,8 @@ import static
org.apache.flink.table.factories.FactoryUtil.FORMAT;
import static org.apache.flink.table.factories.FactoryUtil.SINK_PARALLELISM;
import static org.apache.inlong.sort.base.Constants.*;
import static org.apache.inlong.sort.base.Constants.DATASOURCE_PARTITION_MAP;
+import static org.apache.inlong.sort.base.Constants.PATTERN_PARTITION_MAP;
+import static org.apache.inlong.sort.base.Constants.SINK_MULTIPLE_FORMAT;
import static org.apache.inlong.sort.base.Constants.SINK_SCHEMA_CHANGE_ENABLE;
import static
org.apache.inlong.sort.base.Constants.SINK_SCHEMA_CHANGE_POLICIES;
import static
org.apache.inlong.sort.kafka.table.KafkaOptions.KAFKA_IGNORE_ALL_CHANGELOG;
@@ -124,14 +128,20 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
public static final String IDENTIFIER = "kafka-inlong";
public static final String SINK_PARTITIONER_VALUE_RAW_HASH = "raw-hash";
-
+ public static final String SINK_PARTITIONER_VALUE_PRIMARY_KEY =
"primaryKey";
public static final String SINK_PARTITIONER_VALUE_INLONG_FIXED_PARTITION =
"inlong-fixed-partition";
public static final ConfigOption<String> SINK_MULTIPLE_PARTITION_PATTERN =
ConfigOptions.key("sink.multiple.partition-pattern")
.stringType()
.noDefaultValue()
- .withDescription("option 'sink.multiple.partition-pattern'
used when the partitioner is raw-hash.");
+ .withDescription(
+ "option 'sink.multiple.partition-pattern' used
either when the partitioner is raw-hash, or when passing in designated
partition field names for custom field partitions");
+
+ public static final ConfigOption<String> SINK_FIXED_IDENTIFIER =
+ ConfigOptions.key("sink.fixed.identifier")
+ .stringType()
+ .defaultValue("-1");
private static final Set<String> SINK_SEMANTIC_ENUMS =
new HashSet<>(
@@ -249,7 +259,7 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
}
private Optional<FlinkKafkaPartitioner<RowData>> getFlinkKafkaPartitioner(
- ReadableConfig tableOptions, ClassLoader classLoader) {
+ ReadableConfig tableOptions, ClassLoader classLoader, TableSchema
schema) {
if (tableOptions.getOptional(SINK_PARTITIONER).isPresent() &&
SINK_PARTITIONER_VALUE_INLONG_FIXED_PARTITION
.equals(tableOptions.getOptional(SINK_PARTITIONER).get())) {
InLongFixedPartitionPartitioner<RowData>
inLongFixedPartitionPartitioner =
@@ -260,8 +270,7 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
inLongFixedPartitionPartitioner.setSinkMultipleFormat(tableOptions.getOptional(SINK_MULTIPLE_FORMAT)
.orElse(null));
return Optional.of(inLongFixedPartitionPartitioner);
- }
- if (tableOptions.getOptional(SINK_PARTITIONER).isPresent()
+ } else if (tableOptions.getOptional(SINK_PARTITIONER).isPresent()
&&
SINK_PARTITIONER_VALUE_RAW_HASH.equals(tableOptions.getOptional(SINK_PARTITIONER).get()))
{
RawDataHashPartitioner<RowData> rawHashPartitioner = new
RawDataHashPartitioner<>(tableOptions
.getOptional(DATASOURCE_PARTITION_MAP).orElse(Collections.emptyMap()));
@@ -269,6 +278,16 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
rawHashPartitioner.setPartitionPattern(tableOptions.getOptional(SINK_MULTIPLE_PARTITION_PATTERN)
.orElse(null));
return Optional.of(rawHashPartitioner);
+ } else if (tableOptions.getOptional(SINK_PARTITIONER).isPresent()
+ &&
SINK_PARTITIONER_VALUE_PRIMARY_KEY.equals(tableOptions.getOptional(SINK_PARTITIONER).get()))
{
+ SingleTableCustomFieldsPartitioner<RowData>
customFieldsPartitioner =
+ new SingleTableCustomFieldsPartitioner<>();
+ // the pattern is different from the ${} pattern, it is a
truncated string of schema fields.
+
customFieldsPartitioner.setPartitionNumber(tableOptions.getOptional(SINK_FIXED_IDENTIFIER).orElse(null));
+
customFieldsPartitioner.setPartitionKey(tableOptions.getOptional(SINK_MULTIPLE_PARTITION_PATTERN)
+ .orElse(null));
+ customFieldsPartitioner.setSchema(schema);
+ return Optional.of(customFieldsPartitioner);
}
Optional<FlinkKafkaPartitioner<RowData>> partitioner = KafkaOptions
.getFlinkKafkaPartitioner(tableOptions, classLoader);
@@ -338,6 +357,7 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
options.add(DATASOURCE_PARTITION_MAP);
options.add(SINK_SCHEMA_CHANGE_ENABLE);
options.add(SINK_SCHEMA_CHANGE_POLICIES);
+ options.add(SINK_FIXED_IDENTIFIER);
return options;
}
@@ -472,7 +492,8 @@ public class KafkaDynamicTableFactory implements
DynamicTableSourceFactory, Dyna
tableOptions.get(TOPIC).get(0),
getKafkaProperties(context.getCatalogTable().getOptions()),
context.getCatalogTable(),
- getFlinkKafkaPartitioner(tableOptions,
context.getClassLoader()).orElse(null),
+ getFlinkKafkaPartitioner(tableOptions,
context.getClassLoader(),
+ context.getCatalogTable().getSchema()).orElse(null),
getSinkSemantic(tableOptions),
parallelism,
inlongMetric,
diff --git
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/UpsertKafkaDynamicTableFactory.java
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/UpsertKafkaDynamicTableFactory.java
index 9f527e3a29..cb5b054867 100644
---
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/UpsertKafkaDynamicTableFactory.java
+++
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/main/java/org/apache/inlong/sort/kafka/table/UpsertKafkaDynamicTableFactory.java
@@ -21,6 +21,7 @@ import org.apache.inlong.sort.base.dirty.DirtyOptions;
import org.apache.inlong.sort.base.dirty.sink.DirtySink;
import org.apache.inlong.sort.base.dirty.utils.DirtySinkFactoryUtils;
import org.apache.inlong.sort.kafka.KafkaDynamicSink;
+import org.apache.inlong.sort.kafka.SingleTableCustomFieldsPartitioner;
import org.apache.inlong.sort.protocol.enums.SchemaChangePolicy;
import org.apache.inlong.sort.protocol.enums.SchemaChangeType;
import org.apache.inlong.sort.util.SchemaChangeUtils;
@@ -29,9 +30,11 @@ import
org.apache.flink.api.common.serialization.DeserializationSchema;
import org.apache.flink.api.common.serialization.SerializationSchema;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.ConfigOption;
+import org.apache.flink.configuration.ConfigOptions;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.ReadableConfig;
import org.apache.flink.streaming.connectors.kafka.config.StartupMode;
+import
org.apache.flink.streaming.connectors.kafka.partitioner.FlinkKafkaPartitioner;
import org.apache.flink.streaming.connectors.kafka.table.KafkaOptions;
import org.apache.flink.streaming.connectors.kafka.table.KafkaSinkSemantic;
import org.apache.flink.streaming.connectors.kafka.table.SinkBufferFlushMode;
@@ -59,6 +62,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
@@ -68,6 +72,7 @@ import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.KEY
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.PROPS_BOOTSTRAP_SERVERS;
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.SINK_BUFFER_FLUSH_INTERVAL;
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.SINK_BUFFER_FLUSH_MAX_ROWS;
+import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.SINK_PARTITIONER;
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.TOPIC;
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.VALUE_FIELDS_INCLUDE;
import static
org.apache.flink.streaming.connectors.kafka.table.KafkaOptions.VALUE_FORMAT;
@@ -82,6 +87,7 @@ import static
org.apache.inlong.sort.base.Constants.INLONG_METRIC;
import static org.apache.inlong.sort.base.Constants.SINK_MULTIPLE_FORMAT;
import static org.apache.inlong.sort.base.Constants.SINK_SCHEMA_CHANGE_ENABLE;
import static
org.apache.inlong.sort.base.Constants.SINK_SCHEMA_CHANGE_POLICIES;
+import static
org.apache.inlong.sort.kafka.table.KafkaDynamicTableFactory.SINK_PARTITIONER_VALUE_PRIMARY_KEY;
import static
org.apache.inlong.sort.kafka.table.KafkaOptions.KAFKA_IGNORE_ALL_CHANGELOG;
/**
@@ -96,6 +102,17 @@ public class UpsertKafkaDynamicTableFactory
DynamicTableSinkFactory {
public static final String IDENTIFIER = "upsert-kafka-inlong";
+ public static final ConfigOption<String> SINK_MULTIPLE_PARTITION_PATTERN =
+ ConfigOptions.key("sink.multiple.partition-pattern")
+ .stringType()
+ .noDefaultValue()
+ .withDescription(
+ "option 'sink.multiple.partition-pattern' used
either when the partitioner is raw-hash, or when passing in designated
partition field names for custom field partitions");
+
+ public static final ConfigOption<String> SINK_FIXED_IDENTIFIER =
+ ConfigOptions.key("sink.fixed.identifier")
+ .stringType()
+ .defaultValue("-1");
private static void validateSource(
ReadableConfig tableOptions, Format keyFormat, Format valueFormat,
TableSchema schema) {
@@ -199,6 +216,9 @@ public class UpsertKafkaDynamicTableFactory
options.add(SINK_BUFFER_FLUSH_MAX_ROWS);
options.add(KAFKA_IGNORE_ALL_CHANGELOG);
options.add(INLONG_METRIC);
+ options.add(SINK_PARTITIONER);
+ options.add(SINK_MULTIPLE_PARTITION_PATTERN);
+ options.add(SINK_FIXED_IDENTIFIER);
return options;
}
@@ -250,6 +270,23 @@ public class UpsertKafkaDynamicTableFactory
auditKeys);
}
+ private Optional<FlinkKafkaPartitioner<RowData>> getFlinkKafkaPartitioner(
+ ReadableConfig tableOptions, ClassLoader classLoader, TableSchema
schema) {
+ if (tableOptions.getOptional(SINK_PARTITIONER).isPresent()
+ &&
SINK_PARTITIONER_VALUE_PRIMARY_KEY.equals(tableOptions.getOptional(SINK_PARTITIONER).get()))
{
+ SingleTableCustomFieldsPartitioner<RowData>
customFieldsPartitioner =
+ new SingleTableCustomFieldsPartitioner<>();
+
customFieldsPartitioner.setPartitionNumber(tableOptions.getOptional(SINK_FIXED_IDENTIFIER).orElse(null));
+
customFieldsPartitioner.setPartitionKey(tableOptions.getOptional(SINK_MULTIPLE_PARTITION_PATTERN)
+ .orElse(null));
+ customFieldsPartitioner.setSchema(schema);
+ return Optional.of(customFieldsPartitioner);
+ }
+ Optional<FlinkKafkaPartitioner<RowData>> partitioner = KafkaOptions
+ .getFlinkKafkaPartitioner(tableOptions, classLoader);
+ return partitioner;
+ }
+
@Override
public DynamicTableSink createDynamicTableSink(Context context) {
FactoryUtil.TableFactoryHelper helper =
@@ -289,6 +326,9 @@ public class UpsertKafkaDynamicTableFactory
final boolean enableSchemaChange =
tableOptions.get(SINK_SCHEMA_CHANGE_ENABLE);
final Map<SchemaChangeType, SchemaChangePolicy> policyMap =
enableSchemaChange ?
SchemaChangeUtils.deserialize(schemaChangePolicies) : Collections.emptyMap();
+ final FlinkKafkaPartitioner<RowData> partitioner =
+ getFlinkKafkaPartitioner(tableOptions,
context.getClassLoader(),
+ context.getCatalogTable().getSchema()).orElse(null);
// use {@link
org.apache.kafka.clients.producer.internals.DefaultPartitioner}.
// it will use hash partition if key is set else in round-robin
behaviour.
@@ -303,7 +343,7 @@ public class UpsertKafkaDynamicTableFactory
tableOptions.get(TOPIC).get(0),
properties,
context.getCatalogTable(),
- null,
+ partitioner,
KafkaSinkSemantic.AT_LEAST_ONCE,
true,
flushMode,
diff --git
a/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/test/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitionerTest.java
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/test/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitionerTest.java
new file mode 100644
index 0000000000..1df59dfe5b
--- /dev/null
+++
b/inlong-sort/sort-flink/sort-flink-v1.13/sort-connectors/kafka/src/test/java/org/apache/inlong/sort/kafka/partitioner/SingleTableCustomFieldsPartitionerTest.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.inlong.sort.kafka.partitioner;
+
+import org.apache.inlong.sort.kafka.SingleTableCustomFieldsPartitioner;
+
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.api.TableSchema;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.data.RowData.FieldGetter;
+import org.apache.flink.table.data.binary.BinaryRowData;
+import org.apache.flink.table.data.writer.BinaryRowWriter;
+import org.apache.flink.table.types.logical.IntType;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * The unit tests for {@link
+ *
org.apache.inlong.sort.kafka.partitioner.SingleTableCustomFieldsPartitioner}.
+ */
+public class SingleTableCustomFieldsPartitionerTest {
+
+ @Test
+ public void testCustomFieldsPartitioner() {
+ SingleTableCustomFieldsPartitioner singleTableCustomFieldsPartitioner =
+ new SingleTableCustomFieldsPartitioner();
+
+ TableSchema schema = TableSchema.builder()
+ .field("id", DataTypes.INT())
+ .field("age", DataTypes.INT())
+ .build();
+
+ singleTableCustomFieldsPartitioner.setSchema(schema);
+ singleTableCustomFieldsPartitioner.setPartitionKey("age");
+
+ FieldGetter getter0 = RowData.createFieldGetter(new IntType(), 0);
+ FieldGetter getter1 = RowData.createFieldGetter(new IntType(), 1);
+ FieldGetter[] valuefieldgetters = {getter0, getter1};
+
+
singleTableCustomFieldsPartitioner.setValueFieldGetters(valuefieldgetters);
+
+ BinaryRowData rowData1 = new BinaryRowData(2);
+ BinaryRowWriter writer1 = new BinaryRowWriter(rowData1);
+ BinaryRowData rowData2 = new BinaryRowData(2);
+ BinaryRowWriter writer2 = new BinaryRowWriter(rowData2);
+
+ writer1.writeInt(0, 1);
+ writer1.writeInt(1, 786819156);
+
+ writer2.writeInt(0, 2);
+ writer2.writeInt(1, 786819156);
+
+ // key is null since the actual key is being calculated from
deserialized json
+ int partition1 =
singleTableCustomFieldsPartitioner.partition(rowData1, null,
+ null, null, new int[]{0, 1, 2, 3, 4});
+
+ int partition2 =
singleTableCustomFieldsPartitioner.partition(rowData2, null,
+ null, null, new int[]{0, 1, 2, 3, 4});
+
+ writer1.complete();
+ writer2.complete();
+
+ Assert.assertEquals(partition1, partition2);
+ }
+}