This is an automated email from the ASF dual-hosted git repository.
loserwang1024 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink-cdc.git
The following commit(s) were added to refs/heads/master by this push:
new 24d08ea96 [FLINK-41058][pipeline-connector/fluss] Merge schema options
with global sink table properties when creating tables. (#4587)
24d08ea96 is described below
commit 24d08ea96ad5461c6da8e5cd7892e5956958765b
Author: Hongshun Wang <[email protected]>
AuthorDate: Wed Oct 21 17:28:06 2026 +0800
[FLINK-41058][pipeline-connector/fluss] Merge schema options with global
sink table properties when creating tables. (#4587)
---
.../docs/connectors/pipeline-connectors/fluss.md | 22 ++++++
.../docs/connectors/pipeline-connectors/fluss.md | 24 +++++++
.../fluss/sink/FlussMetaDataApplier.java | 5 +-
.../fluss/sink/FlussMetadataApplierTest.java | 79 +++++++++++++++++++++-
4 files changed, 128 insertions(+), 2 deletions(-)
diff --git a/docs/content.zh/docs/connectors/pipeline-connectors/fluss.md
b/docs/content.zh/docs/connectors/pipeline-connectors/fluss.md
index 9196e9024..d0399225b 100644
--- a/docs/content.zh/docs/connectors/pipeline-connectors/fluss.md
+++ b/docs/content.zh/docs/connectors/pipeline-connectors/fluss.md
@@ -160,6 +160,28 @@ Pipeline Connector Options
</table>
</div>
+### 在 transform 规则中配置表参数
+
+可以通过 transform 规则中的 `table-options` 为匹配的表配置 Fluss 表参数。
+参数名直接使用 `table.replication.factor` 等 Fluss 表属性名称,无需添加 sink 配置中的 `properties.`
前缀。
+这些参数会覆盖同名的 `sink.properties.table.*` 配置,未覆盖的 sink 表参数仍然生效。
+
+```yaml
+transform:
+ - source-table: inventory.products
+ projection: "*"
+ table-options: table.replication.factor=2,table.log.ttl=7d
+
+sink:
+ type: fluss
+ bootstrap.servers: localhost:9123
+ properties.table.replication.factor: 3
+```
+
+在此示例中,为 `inventory.products` 创建的 Fluss 表副本数为 2,日志保留时间为 7 天,其他表的副本数为 3。
+这些参数仅在建表时生效,不会修改已有表的属性。
+分桶键和桶数量仍通过 sink 的 `bucket.key` 和 `bucket.num` 配置。
+
## 使用说明
* 支持 Fluss 主键表和日志表。
diff --git a/docs/content/docs/connectors/pipeline-connectors/fluss.md
b/docs/content/docs/connectors/pipeline-connectors/fluss.md
index 041813738..e43bee43b 100644
--- a/docs/content/docs/connectors/pipeline-connectors/fluss.md
+++ b/docs/content/docs/connectors/pipeline-connectors/fluss.md
@@ -163,6 +163,30 @@ Pipeline Connector Options
</table>
</div>
+### Table options in transform rules
+
+Use `table-options` in a transform rule to configure Fluss table properties
for the matched table.
+Use Fluss property names such as `table.replication.factor`, without the
`properties.` prefix used
+in the sink configuration. These options override matching
`sink.properties.table.*` options;
+other sink table properties still apply.
+
+```yaml
+transform:
+ - source-table: inventory.products
+ projection: "*"
+ table-options: table.replication.factor=2,table.log.ttl=7d
+
+sink:
+ type: fluss
+ bootstrap.servers: localhost:9123
+ properties.table.replication.factor: 3
+```
+
+In this example, the Fluss table created for `inventory.products` has a
replication factor of 2
+and a log retention time of 7 days. Other tables use a replication factor of 3.
+These options apply when creating tables and do not update the properties of
existing tables.
+Continue to configure bucket keys and bucket counts with the sink options
`bucket.key` and `bucket.num`.
+
## Usage Notes
* Support Fluss primary key table and log table.
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/main/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetaDataApplier.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/main/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetaDataApplier.java
index 3ec152430..3f6c9fc45 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/main/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetaDataApplier.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/main/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetaDataApplier.java
@@ -50,6 +50,7 @@ import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -171,8 +172,10 @@ public class FlussMetaDataApplier implements
MetadataApplier, ExistingTableSchem
String tableIdentifier = tablePath.getDatabaseName() + "." +
tablePath.getTableName();
List<String> bucketKeys = bucketKeysMap.get(tableIdentifier);
Integer bucketNum = bucketNumMap.get(tableIdentifier);
+ Map<String, String> fullTableProperties = new
HashMap<>(tableProperties);
+ fullTableProperties.putAll(event.getSchema().options());
TableDescriptor inferredFlussTable =
- toFlussTable(event.getSchema(), bucketKeys, bucketNum,
tableProperties);
+ toFlussTable(event.getSchema(), bucketKeys, bucketNum,
fullTableProperties);
admin.createDatabase(tablePath.getDatabaseName(),
DatabaseDescriptor.EMPTY, true);
if (!admin.tableExists(tablePath).get()) {
admin.createTable(tablePath, inferredFlussTable, false).get();
diff --git
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/test/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetadataApplierTest.java
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/test/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetadataApplierTest.java
index 1faf881f8..ed2ab81fc 100644
---
a/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/test/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetadataApplierTest.java
+++
b/flink-cdc-connect/flink-cdc-pipeline-connectors/flink-cdc-pipeline-connector-fluss/src/test/java/org/apache/flink/cdc/connectors/fluss/sink/FlussMetadataApplierTest.java
@@ -57,6 +57,7 @@ import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import java.lang.reflect.Proxy;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -71,13 +72,15 @@ import java.util.stream.Stream;
import static
org.apache.flink.cdc.connectors.fluss.utils.FlussConversions.toFlussSchema;
import static
org.apache.flink.cdc.connectors.fluss.utils.FlussConversions.toFlussType;
+import static org.apache.fluss.config.ConfigOptions.DEFAULT_REPLICATION_FACTOR;
+import static org.apache.fluss.config.ConfigOptions.TABLE_LOG_TTL;
import static org.apache.fluss.config.ConfigOptions.TABLE_REPLICATION_FACTOR;
import static org.apache.fluss.types.DataTypeChecks.equalsWithFieldId;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/** Test for {@link FlussMetaDataApplier}. */
-public class FlussMetadataApplierTest {
+class FlussMetadataApplierTest {
@RegisterExtension
private static final FlussClusterExtension FLUSS_CLUSTER_EXTENSION =
FlussClusterExtension.builder().setNumOfTabletServers(3).build();
@@ -1204,4 +1207,78 @@ public class FlussMetadataApplierTest {
assertThat(tableInfo.getProperties().get(TABLE_REPLICATION_FACTOR)).isEqualTo(5);
}
}
+
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void testCreateTableWithSchemaOptions(boolean hasSinkOptions) throws
Exception {
+ Map<String, String> tableProperties =
+ hasSinkOptions
+ ? Map.of(TABLE_REPLICATION_FACTOR.key(), "3",
TABLE_LOG_TTL.key(), "7 d")
+ : Collections.emptyMap();
+ Schema schema =
+ Schema.newBuilder()
+ .physicalColumn("id", DataTypes.INT().notNull())
+ .primaryKey("id")
+ .option(TABLE_REPLICATION_FACTOR.key(), "2")
+ .build();
+ TableId tableId = TableId.tableId(DATABASE_NAME, "table1");
+ try (FlussMetaDataApplier applier =
+ new FlussMetaDataApplier(
+ FLUSS_CLUSTER_EXTENSION.getClientConfig(),
+ tableProperties,
+ Collections.emptyMap(),
+ Collections.emptyMap())) {
+ applier.applySchemaChange(new CreateTableEvent(tableId, schema));
+ TableInfo tableInfo = admin.getTableInfo(new
TablePath(DATABASE_NAME, "table1")).get();
+
assertThat(tableInfo.getProperties().get(TABLE_REPLICATION_FACTOR)).isEqualTo(2);
+ if (hasSinkOptions) {
+ assertThat(tableInfo.getProperties().get(TABLE_LOG_TTL))
+ .isEqualTo(Duration.ofDays(7));
+ }
+ assertThat(schema.options())
+ .containsExactlyEntriesOf(
+
Collections.singletonMap(TABLE_REPLICATION_FACTOR.key(), "2"));
+
+ Schema schemaWithoutOptions =
+ Schema.newBuilder()
+ .physicalColumn("id", DataTypes.INT().notNull())
+ .primaryKey("id")
+ .build();
+ applier.applySchemaChange(
+ new CreateTableEvent(
+ TableId.tableId(DATABASE_NAME, "table2"),
schemaWithoutOptions));
+ TableInfo otherTableInfo =
+ admin.getTableInfo(new TablePath(DATABASE_NAME,
"table2")).get();
+
assertThat(otherTableInfo.getProperties().get(TABLE_REPLICATION_FACTOR))
+ .isEqualTo(hasSinkOptions ? 3 :
DEFAULT_REPLICATION_FACTOR.defaultValue());
+ }
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {"key", "properties.table.replication.factor"})
+ void testCreateTableWithInvalidSchemaOptions(String optionKey) throws
Exception {
+ Schema schema =
+ Schema.newBuilder()
+ .physicalColumn("id", DataTypes.INT().notNull())
+ .primaryKey("id")
+ .option(optionKey, "2")
+ .build();
+ try (FlussMetaDataApplier applier =
+ new FlussMetaDataApplier(
+ FLUSS_CLUSTER_EXTENSION.getClientConfig(),
+ Collections.emptyMap(),
+ Collections.emptyMap(),
+ Collections.emptyMap())) {
+ assertThatThrownBy(
+ () ->
+ applier.applySchemaChange(
+ new CreateTableEvent(
+
TableId.tableId(DATABASE_NAME, "table1"),
+ schema)))
+ .rootCause()
+ .isExactlyInstanceOf(InvalidConfigException.class)
+ .hasMessageContaining("'" + optionKey + "' is not a Fluss
table property");
+ assertThat(admin.tableExists(new TablePath(DATABASE_NAME,
"table1")).get()).isFalse();
+ }
+ }
}