This is an automated email from the ASF dual-hosted git repository.
shuwenwei pushed a commit to branch flink-iotdb-table-connector
in repository https://gitbox.apache.org/repos/asf/iotdb-extras.git
The following commit(s) were added to refs/heads/flink-iotdb-table-connector by
this push:
new 9e5206f fix: apply CDC projection in iterator and reject CDC pushdown
9e5206f is described below
commit 9e5206f98604b040de208581036b97d436549efc
Author: shuwenwei <[email protected]>
AuthorDate: Wed Sep 23 16:27:05 2026 +0800
fix: apply CDC projection in iterator and reject CDC pushdown
Move projection into SubscriptionDataIterator so deserializers read output
positions, and reject filter/aggregate/limit pushdown for the CDC source.
---
.../flink/source/cdc/SubscriptionDataIterator.java | 66 +++++++++++++++++-----
.../flink/source/common/IoTDBDataIterator.java | 5 ++
.../flink/source/cdc/IoTDBCDCSource.java | 11 +++-
.../source/cdc/IoTDBSubscriptionSourceReader.java | 6 +-
.../table/IoTDBRelationalDynamicTableSource.java | 16 +++++-
5 files changed, 87 insertions(+), 17 deletions(-)
diff --git
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/cdc/SubscriptionDataIterator.java
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/cdc/SubscriptionDataIterator.java
index 1b1b785..bee63a7 100644
---
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/cdc/SubscriptionDataIterator.java
+++
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/cdc/SubscriptionDataIterator.java
@@ -25,15 +25,55 @@ import
org.apache.iotdb.session.subscription.payload.SubscriptionRecordHandler.S
import java.io.IOException;
import java.sql.Timestamp;
import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
-/** Adapts a subscription {@link SubscriptionResultSet} to the unified {@link
IoTDBDataIterator}. */
+/**
+ * Adapts a subscription {@link SubscriptionResultSet} to the unified {@link
IoTDBDataIterator}.
+ *
+ * <p>Projection is applied here: the iterator exposes only the projected
columns, in the projected
+ * order, and maps them onto the full subscription record. Deserializers can
therefore read the
+ * columns by their output position without knowing about the projection.
+ */
public class SubscriptionDataIterator implements IoTDBDataIterator {
private final SubscriptionResultSet resultSet;
+ private final List<String> columnNames;
+ private final int[] sourceIndexes;
- public SubscriptionDataIterator(SubscriptionResultSet resultSet) {
+ public SubscriptionDataIterator(
+ SubscriptionResultSet resultSet, List<String> projectedColumns) {
this.resultSet = resultSet;
+ this.columnNames = Collections.unmodifiableList(new
ArrayList<>(projectedColumns));
+ this.sourceIndexes = resolveSourceIndexes(resultSet.getColumnNames(),
projectedColumns);
+ }
+
+ private static int[] resolveSourceIndexes(
+ List<String> availableColumns, List<String> projectedColumns) {
+ Map<String, Integer> indexByName = new HashMap<>(availableColumns.size());
+ for (int i = 0; i < availableColumns.size(); i++) {
+ indexByName.put(normalize(availableColumns.get(i)), i);
+ }
+ int[] indexes = new int[projectedColumns.size()];
+ for (int i = 0; i < projectedColumns.size(); i++) {
+ Integer index = indexByName.get(normalize(projectedColumns.get(i)));
+ if (index == null) {
+ throw new IllegalArgumentException(
+ "Projected column '"
+ + projectedColumns.get(i)
+ + "' does not exist in the IoTDB result set.");
+ }
+ indexes[i] = index;
+ }
+ return indexes;
+ }
+
+ private static String normalize(String columnName) {
+ return columnName.trim().toLowerCase(Locale.ROOT);
}
@Override
@@ -43,56 +83,56 @@ public class SubscriptionDataIterator implements
IoTDBDataIterator {
@Override
public List<String> getColumnNames() {
- return resultSet.getColumnNames();
+ return columnNames;
}
@Override
public boolean isNull(int columnIndex) {
- return resultSet.isNull(columnIndex);
+ return resultSet.isNull(sourceIndexes[columnIndex]);
}
@Override
public boolean getBoolean(int columnIndex) {
- return resultSet.getBoolean(columnIndex);
+ return resultSet.getBoolean(sourceIndexes[columnIndex]);
}
@Override
public int getInt(int columnIndex) {
- return resultSet.getInt(columnIndex);
+ return resultSet.getInt(sourceIndexes[columnIndex]);
}
@Override
public long getLong(int columnIndex) {
- return resultSet.getLong(columnIndex);
+ return resultSet.getLong(sourceIndexes[columnIndex]);
}
@Override
public float getFloat(int columnIndex) {
- return resultSet.getFloat(columnIndex);
+ return resultSet.getFloat(sourceIndexes[columnIndex]);
}
@Override
public double getDouble(int columnIndex) {
- return resultSet.getDouble(columnIndex);
+ return resultSet.getDouble(sourceIndexes[columnIndex]);
}
@Override
public String getString(int columnIndex) {
- return resultSet.getString(columnIndex);
+ return resultSet.getString(sourceIndexes[columnIndex]);
}
@Override
public byte[] getBinary(int columnIndex) {
- return resultSet.getBinary(columnIndex);
+ return resultSet.getBinary(sourceIndexes[columnIndex]);
}
@Override
public LocalDate getDate(int columnIndex) {
- return resultSet.getDate(columnIndex);
+ return resultSet.getDate(sourceIndexes[columnIndex]);
}
@Override
public Timestamp getTimestamp(int columnIndex) {
- return new Timestamp(resultSet.getLong(columnIndex));
+ return new Timestamp(resultSet.getLong(sourceIndexes[columnIndex]));
}
}
diff --git
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/common/IoTDBDataIterator.java
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/common/IoTDBDataIterator.java
index c334dd8..1515cb2 100644
---
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/common/IoTDBDataIterator.java
+++
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-base/src/main/java/org/apache/iotdb/relational/flink/source/common/IoTDBDataIterator.java
@@ -29,6 +29,11 @@ import java.util.List;
*
* <p>The caller advances with {@link #next()} and then reads the current row
by column index. The
* index is always 0-based, regardless of the underlying IoTDB API.
+ *
+ * <p>The exposed columns are the final output columns of the source:
projection has already been
+ * applied. Implementations are responsible for hiding the projection (e.g.
the CDC iterator maps
+ * the projected positions onto the full subscription record), so a {@link
+ * IoTDBDeserializationSchema} can simply read the columns by their output
position.
*/
public interface IoTDBDataIterator {
diff --git
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBCDCSource.java
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBCDCSource.java
index 379b216..50df175 100644
---
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBCDCSource.java
+++
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBCDCSource.java
@@ -34,6 +34,7 @@ import
org.apache.flink.api.connector.source.SourceReaderContext;
import org.apache.flink.api.connector.source.SplitEnumerator;
import org.apache.flink.api.connector.source.SplitEnumeratorContext;
import org.apache.flink.core.io.SimpleVersionedSerializer;
+import org.apache.flink.table.types.DataType;
/**
* Unbounded CDC source backed by the IoTDB subscription API.
@@ -48,10 +49,15 @@ public class IoTDBCDCSource<OUT>
private static final long serialVersionUID = 1L;
private final IoTDBOptions options;
+ private final DataType projectedRowType;
private final IoTDBDeserializationSchema<OUT> deserializer;
- public IoTDBCDCSource(IoTDBOptions options, IoTDBDeserializationSchema<OUT>
deserializer) {
+ public IoTDBCDCSource(
+ IoTDBOptions options,
+ DataType projectedRowType,
+ IoTDBDeserializationSchema<OUT> deserializer) {
this.options = options;
+ this.projectedRowType = projectedRowType;
this.deserializer = deserializer;
}
@@ -62,7 +68,8 @@ public class IoTDBCDCSource<OUT>
@Override
public SourceReader<OUT, IoTDBSubscriptionSplit>
createReader(SourceReaderContext readerContext) {
- return new IoTDBSubscriptionSourceReader<>(readerContext, options,
deserializer);
+ return new IoTDBSubscriptionSourceReader<>(
+ readerContext, options, projectedRowType, deserializer);
}
@Override
diff --git
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBSubscriptionSourceReader.java
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBSubscriptionSourceReader.java
index a668bc0..878759d 100644
---
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBSubscriptionSourceReader.java
+++
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/source/cdc/IoTDBSubscriptionSourceReader.java
@@ -32,6 +32,7 @@ import org.apache.flink.api.connector.source.ReaderOutput;
import org.apache.flink.api.connector.source.SourceReader;
import org.apache.flink.api.connector.source.SourceReaderContext;
import org.apache.flink.core.io.InputStatus;
+import org.apache.flink.table.types.DataType;
import org.apache.tsfile.read.query.dataset.ResultSet;
import java.time.Duration;
@@ -52,6 +53,7 @@ public class IoTDBSubscriptionSourceReader<OUT>
private final SourceReaderContext context;
private final IoTDBOptions options;
+ private final List<String> projectedColumns;
private final IoTDBDeserializationSchema<OUT> deserializer;
private final Deque<IoTDBSubscriptionSplit> pendingSplits = new
ArrayDeque<>();
private final Deque<OUT> buffer = new ArrayDeque<>();
@@ -65,9 +67,11 @@ public class IoTDBSubscriptionSourceReader<OUT>
public IoTDBSubscriptionSourceReader(
SourceReaderContext context,
IoTDBOptions options,
+ DataType projectedRowType,
IoTDBDeserializationSchema<OUT> deserializer) {
this.context = context;
this.options = options;
+ this.projectedColumns = DataType.getFieldNames(projectedRowType);
this.deserializer = deserializer;
}
@@ -172,7 +176,7 @@ public class IoTDBSubscriptionSourceReader<OUT>
}
for (ResultSet resultSet : message.getResultSets()) {
SubscriptionDataIterator iterator =
- new SubscriptionDataIterator((SubscriptionResultSet) resultSet);
+ new SubscriptionDataIterator((SubscriptionResultSet) resultSet,
projectedColumns);
while (iterator.next()) {
OUT record = deserializer.deserialize(iterator);
if (record != null) {
diff --git
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/table/IoTDBRelationalDynamicTableSource.java
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/table/IoTDBRelationalDynamicTableSource.java
index 966651f..b7cbaac 100644
---
a/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/table/IoTDBRelationalDynamicTableSource.java
+++
b/connectors/flink-iotdb-table-connector/flink-iotdb-table-connector-flink1/src/main/java/org/apache/iotdb/relational/flink/table/IoTDBRelationalDynamicTableSource.java
@@ -104,7 +104,7 @@ public class IoTDBRelationalDynamicTableSource
if (options.isCdc()) {
return SourceProvider.of(
new IoTDBCDCSource<RowData>(
- options, new RowDataDeserializationSchema(physicalRowDataType)));
+ options, physicalRowDataType, new
RowDataDeserializationSchema(physicalRowDataType)));
}
return SourceProvider.of(
new IoTDBSource<>(
@@ -172,6 +172,11 @@ public class IoTDBRelationalDynamicTableSource
return Result.of(Collections.emptyList(), Collections.emptyList());
}
+ // The CDC source reads the subscription stream and cannot apply row-level
filters.
+ if (options.isCdc()) {
+ return Result.of(Collections.emptyList(), filters);
+ }
+
List<ResolvedExpression> acceptedFilters = new ArrayList<>();
List<ResolvedExpression> remainingFilters = new ArrayList<>();
IoTDBExpressionVisitor expressionVisitor = new IoTDBExpressionVisitor();
@@ -192,6 +197,10 @@ public class IoTDBRelationalDynamicTableSource
List<int[]> groupingSets,
List<AggregateExpression> aggregateExpressions,
DataType producedDataType) {
+ // The CDC source cannot aggregate an unbounded subscription stream.
+ if (options.isCdc()) {
+ return false;
+ }
// Grouping and argument indices refer to the scan's current row type,
which is the row type
// after any projection that has already been pushed into this source.
AggregateSpec spec =
@@ -207,6 +216,11 @@ public class IoTDBRelationalDynamicTableSource
@Override
public void applyLimit(long limit) {
+ // Limit pushdown is only an optimization hint; the CDC source ignores it
and Flink still
+ // enforces the limit itself.
+ if (options.isCdc()) {
+ return;
+ }
if (aggregateSpec == null) {
this.limit = limit;
}