Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
41ecc50
[kafka] Serve ApiVersions from registered capabilities
gyang94 Sep 8, 2026
4317d87
[kafka] Cover ApiVersions wire negotiation and capability ranges
gyang94 Oct 10, 2026
229fc94
[kafka] Adapt ApiVersions to immutable API registry
gyang94 Oct 13, 2026
10bf516
[kafka] Adapt version fallback regression to PR02 handler
gyang94 Oct 13, 2026
062047d
[kafka] Clarify uninitialized API registry failures
gyang94 Oct 13, 2026
3496440
[kafka] Define DDL table mapping for Kafka compatibility
gyang94 Sep 10, 2026
3252d38
[kafka] Route qualified topics and streamline field projections
gyang94 Sep 17, 2026
1275b33
[kafka] Define Kafka timestamp metadata as TIMESTAMP(3)
gyang94 Sep 21, 2026
dea30c2
[test] Wait for table readiness in snapshot lease validation
gyang94 Oct 11, 2026
6e9dc55
[kafka] Align DDL table mapping with the FIP contract
gyang94 Oct 12, 2026
48c84f9
[kafka] Validate recursive JSON schemas and default to fluss
gyang94 Oct 12, 2026
e7673be
[kafka] Enable metadata mapping and fix table change notifications
gyang94 Oct 14, 2026
b4e5a02
[kafka] Focus DDL mapping changes and streamline tests
gyang94 Oct 14, 2026
7bcda8b
[kafka] Simplify DDL validation and default database setup
gyang94 Oct 14, 2026
2b6fb3a
[kafka] Use the DDL table contract in Metadata
gyang94 Sep 10, 2026
947c119
[kafka] Discover qualified topics and isolate missing databases
gyang94 Sep 17, 2026
14b73e9
[kafka] Preserve Metadata regressions on the updated PR03 base
gyang94 Oct 10, 2026
02d5074
[test] Wait for coordinator metadata before snapshot lease retry
gyang94 Oct 12, 2026
5439329
[kafka] Apply persisted topic enablement to Metadata
gyang94 Oct 12, 2026
2cf97cd
[kafka] Give topic aliases distinct stable wire IDs
gyang94 Oct 12, 2026
408a85b
[kafka] Validate Metadata against the updated PR03 contract
gyang94 Oct 14, 2026
ecbb368
[kafka] Simplify Metadata wiring and regression tests
gyang94 Oct 14, 2026
3950435
[kafka] Add independently testable Produce protocol handling
gyang94 Sep 10, 2026
42c427f
[kafka] Validate Produce batch offsets and stream compressed records
gyang94 Sep 17, 2026
65804e7
[kafka] Preserve batch timestamp types in Produce commands
gyang94 Oct 10, 2026
0dde872
[kafka] Verify Produce topic aliases preserve request names
gyang94 Oct 12, 2026
e40fc47
[kafka] Transcode mapped raw and string records to Arrow
gyang94 Sep 10, 2026
0d7a442
[kafka] Stream record transcoding and reuse owned payloads
gyang94 Sep 17, 2026
3cde643
[kafka] Encode timestamp metadata as UTC TIMESTAMP values
gyang94 Sep 21, 2026
b59b527
[kafka] Transcode binary and string into fixed system columns
gyang94 Oct 12, 2026
675bd55
[kafka] Preserve metadata rejection on the updated PR05 base
gyang94 Oct 14, 2026
ef27192
[kafka] Transcode three-field metadata from Produce records
gyang94 Oct 14, 2026
af46cfc
[kafka] Connect Produce to native Fluss append
gyang94 Sep 10, 2026
b7c0f29
[kafka] Cache Produce plans and bound conversion execution
gyang94 Sep 17, 2026
433813e
[kafka] Skip null Produce values with write authorization
gyang94 Sep 21, 2026
7a9d08a
[kafka] Verify Produce boundaries on the updated FIP base
gyang94 Oct 10, 2026
2beeb9f
[kafka] Apply persisted table contracts to Produce append
gyang94 Oct 12, 2026
5eca8c0
[kafka] Wait for replicas before no-ack Produce tests
gyang94 Oct 14, 2026
29d9093
[kafka] Decouple Produce append from delayed fetch wakeups
gyang94 Oct 16, 2026
af1ec7e
[kafka] Persist null Produce values with native offsets
gyang94 Oct 16, 2026
7ec8784
[kafka] Cover Produce lifecycle and replication regressions
gyang94 Sep 11, 2026
a9142d4
[kafka] Cover online leader handoff and qualified topics
gyang94 Sep 18, 2026
a7ce6a7
[kafka] Verify null value filtering with native raw and string readback
gyang94 Sep 21, 2026
ab1ada7
[kafka] Wait for bucket readiness in Produce round trips
gyang94 Oct 10, 2026
cdfb772
[kafka] Validate Produce contracts with isolated client matrix
gyang94 Oct 12, 2026
48d1d0f
[kafka] Verify metadata across schema changes and leader recovery
gyang94 Oct 15, 2026
6da9e21
[kafka] Verify null-value persistence and native offsets
gyang94 Oct 16, 2026
c715c6a
[kafka] Separate value decoding and preserve record metadata
gyang94 Sep 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@

import org.apache.fluss.client.Connection;
import org.apache.fluss.client.ConnectionFactory;
import org.apache.fluss.client.FlussConnection;
import org.apache.fluss.client.metadata.KvSnapshotMetadata;
import org.apache.fluss.client.metadata.KvSnapshots;
import org.apache.fluss.client.metadata.MetadataUpdater;
import org.apache.fluss.client.metadata.TestingMetadataUpdater;
import org.apache.fluss.client.table.Table;
import org.apache.fluss.client.table.writer.AppendWriter;
Expand Down Expand Up @@ -1211,19 +1213,24 @@ void testKvSnapshotLeaseAfterCoordinatorServerRestart() throws Exception {
assertThat(zkClient.getKvSnapshotLeaseMetadata(lease.leaseId())).isPresent();

restartCoordinatorServer(zkClient);
FLUSS_CLUSTER_EXTENSION.waitUntilAllGatewayHasSameMetadata();

lease.dropLease().get();
assertThat(zkClient.getKvSnapshotLeaseMetadata(lease.leaseId())).isNotPresent();
}

private void restartCoordinatorServer(ZooKeeperClient zkClient) throws Exception {
MetadataUpdater metadataUpdater = ((FlussConnection) conn).getMetadataUpdater();
ServerNode cachedCoordinator = metadataUpdater.getCoordinatorServer();
FLUSS_CLUSTER_EXTENSION.stopCoordinatorServer();
waitUntil(
() -> !zkClient.getCoordinatorLeaderAddress().isPresent(),
Duration.ofMinutes(1),
"Coordinator server node still exists in ZooKeeper");
FLUSS_CLUSTER_EXTENSION.startCoordinatorServer();
// Election completes before the new coordinator address reaches every tablet server.
// Wait for server metadata to converge so that the client's retry can discover it.
FLUSS_CLUSTER_EXTENSION.waitUntilAllGatewayHasSameMetadata();
// Keep the client cache stale to exercise the lease's metadata refresh and retry.
assertThat(metadataUpdater.getCoordinatorServer()).isEqualTo(cachedCoordinator);
}

@Test
Expand Down Expand Up @@ -2831,6 +2838,7 @@ void testKvSnapshotLeaseForLogTable() throws Exception {
TableDescriptor logTableDescriptor =
TableDescriptor.builder().schema(logSchema).distributedBy(3).build();
long tableId = createTable(logTablePath, logTableDescriptor, true);
FLUSS_CLUSTER_EXTENSION.waitUntilTableReady(tableId);

// Create a KvSnapshotLease
KvSnapshotLease lease = admin.createKvSnapshotLease("test-lease-log", 60000);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,38 @@ public class ConfigOptions {
+ "Downstream consumers must be upgraded to Fluss v1.0+ before enabling this option, "
+ "as older versions cannot parse the extended batch format.");

public static final ConfigOption<Boolean> TABLE_KAFKA_ENABLED =
key("table.kafka.enabled")
.booleanType()
.defaultValue(false)
.withDescription(
"Whether the table is exposed as a Kafka topic. Enabling validates the "
+ "existing schema and conversion options without adding columns or defaults.");

public static final ConfigOption<String> TABLE_KAFKA_CONVERT_VALUE_TYPE =
key("table.kafka.convert.value.type")
.stringType()
.noDefaultValue()
.withDescription(
"Required when Kafka compatibility is enabled: binary, string, or parse_json. "
+ "Binary and string target the declared _kafka_value column.");

public static final ConfigOption<String> TABLE_KAFKA_CONVERT_VALUE_LAYOUT =
key("table.kafka.convert.value.layout")
.stringType()
.noDefaultValue()
.withDescription(
"JSON layout: flat (the read-time default for parse_json) or nested. "
+ "Nested targets _kafka_value ROW. Not allowed for binary or string.");

public static final ConfigOption<String> TABLE_KAFKA_CONVERT_KEY_TYPE =
key("table.kafka.convert.key.type")
.stringType()
.noDefaultValue()
.withDescription(
"Optional Kafka key conversion type. Only binary is supported and requires "
+ "_kafka_key BYTES. Declaring that column implies binary without persisting a default.");

// ------------------------------------------------------------------------
// ConfigOptions for Kv
// ------------------------------------------------------------------------
Expand Down Expand Up @@ -2905,12 +2937,15 @@ public class ConfigOptions {
.withDescription(
"The listener names for Kafka wire protocol communication. Support multiple listener names, separated by comma.");

public static final ConfigOption<String> KAFKA_DATABASE =
key("kafka.database")
public static final ConfigOption<String> KAFKA_DEFAULT_DATABASE =
key("kafka.default.database")
.stringType()
.defaultValue("kafka")
.defaultValue("fluss")
.withDescription(
"The database for fluss kafka. The default database is `kafka`.");
"Default database for unqualified Kafka topic names. Fully qualified names may "
+ "access other databases. When Kafka is enabled, a configured database other "
+ "than 'fluss' is created if missing. The native 'fluss' database is created "
+ "only when the cluster has no databases.");

/**
* Compaction style for Fluss's kv, which is same to rocksdb's, but help use avoid including
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ public class FlussConfigUtils {
CLIENT_OPTIONS = extractConfigOptions("client.");
ALTERABLE_TABLE_OPTIONS =
Arrays.asList(
ConfigOptions.TABLE_KAFKA_ENABLED.key(),
ConfigOptions.TABLE_KAFKA_CONVERT_VALUE_TYPE.key(),
ConfigOptions.TABLE_KAFKA_CONVERT_VALUE_LAYOUT.key(),
ConfigOptions.TABLE_KAFKA_CONVERT_KEY_TYPE.key(),
ConfigOptions.TABLE_DATALAKE_ENABLED.key(),
ConfigOptions.TABLE_DATALAKE_HISTORICAL_PARTITION_ENABLED.key(),
ConfigOptions.TABLE_DATALAKE_HISTORICAL_PARTITION_LOOKUP_MODE.key(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public TableConfig(Configuration config) {
this.config = config;
}

/** Whether the table explicitly enables Kafka compatibility. */
public boolean isKafkaEnabled() {
return config.get(ConfigOptions.TABLE_KAFKA_ENABLED);
}

/** Gets the replication factor of the table. */
public int getReplicationFactor() {
return config.get(ConfigOptions.TABLE_REPLICATION_FACTOR);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.fluss.metadata;

import org.apache.fluss.annotation.Internal;
import org.apache.fluss.types.DataType;
import org.apache.fluss.types.RowType;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

import static org.apache.fluss.utils.Preconditions.checkArgument;
import static org.apache.fluss.utils.Preconditions.checkNotNull;

/** Ordered physical Fluss fields populated by one Kafka record component. */
@Internal
public final class KafkaFieldProjection {

private final List<Integer> positions;
private final List<String> names;
private final List<DataType> dataTypes;

/** Creates a projection from physical row positions. */
public KafkaFieldProjection(RowType rowType, List<Integer> positions) {
checkNotNull(rowType);
checkNotNull(positions);
List<Integer> positionCopy = new ArrayList<>(positions.size());
List<String> projectedNames = new ArrayList<>(positions.size());
List<DataType> projectedTypes = new ArrayList<>(positions.size());
for (Integer position : positions) {
checkArgument(
position != null && position >= 0 && position < rowType.getFieldCount(),
"Invalid Kafka field projection position %s.",
position);
positionCopy.add(position);
projectedNames.add(rowType.getFields().get(position).getName());
projectedTypes.add(rowType.getTypeAt(position));
}
this.positions = Collections.unmodifiableList(positionCopy);
this.names = Collections.unmodifiableList(projectedNames);
this.dataTypes = Collections.unmodifiableList(projectedTypes);
}

/** Returns the number of projected fields. */
public int size() {
return positions.size();
}

/** Returns whether this projection owns no fields. */
public boolean isEmpty() {
return positions.isEmpty();
}

/** Returns the physical row position at the projection position. */
public int positionAt(int projectionPosition) {
return positions.get(projectionPosition);
}

/** Returns the physical field name at the projection position. */
public String nameAt(int projectionPosition) {
return names.get(projectionPosition);
}

/** Returns the physical data type at the projection position. */
public DataType dataTypeAt(int projectionPosition) {
return dataTypes.get(projectionPosition);
}

/** Returns the projected physical positions. */
public List<Integer> positions() {
return positions;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof KafkaFieldProjection)) {
return false;
}
KafkaFieldProjection that = (KafkaFieldProjection) obj;
return Objects.equals(positions, that.positions)
&& Objects.equals(names, that.names)
&& Objects.equals(dataTypes, that.dataTypes);
}

@Override
public int hashCode() {
return Objects.hash(positions, names, dataTypes);
}

@Override
public String toString() {
return "KafkaFieldProjection{"
+ "positions="
+ positions
+ ", "
+ "names="
+ names
+ ", "
+ "dataTypes="
+ dataTypes
+ "}";
}
}
Loading
Loading