This is an automated email from the ASF dual-hosted git repository.

gyfora pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new d851e0a897a [FLINK-41040][state-processor-api] Fix binary row 
conversion.
d851e0a897a is described below

commit d851e0a897a68655f061b096bb70c6d245b10adc
Author: Muhammad Junaid Muzammil <[email protected]>
AuthorDate: Tue Oct 20 04:36:34 2026 -0700

    [FLINK-41040][state-processor-api] Fix binary row conversion.
---
 .../flink/state/table/StateValueConverter.java     |  32 ++++++
 .../flink/state/table/StateValueConverterTest.java | 113 +++++++++++++++++++++
 2 files changed, 145 insertions(+)

diff --git 
a/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/table/StateValueConverter.java
 
b/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/table/StateValueConverter.java
index 40b9c0d8b7e..19781fe35bb 100644
--- 
a/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/table/StateValueConverter.java
+++ 
b/flink-libraries/flink-state-processing-api/src/main/java/org/apache/flink/state/table/StateValueConverter.java
@@ -71,6 +71,7 @@ class StateValueConverter implements java.io.Serializable {
     // field/getter is only ever resolved via reflection once per class 
instead of on every row.
     private transient Map<Tuple2<Class, String>, Optional<Field>> 
classFieldCache;
     private transient Map<Tuple2<Class, String>, Optional<Method>> 
classMethodCache;
+    private transient Map<RowType, RowData.FieldGetter[]> rowFieldGetterCache;
 
     private Map<Tuple2<Class, String>, Optional<Field>> classFieldCache() {
         if (classFieldCache == null) {
@@ -86,6 +87,22 @@ class StateValueConverter implements java.io.Serializable {
         return classMethodCache;
     }
 
+    private RowData.FieldGetter[] rowFieldGetters(RowType rowType) {
+        if (rowFieldGetterCache == null) {
+            rowFieldGetterCache = new ConcurrentHashMap<>();
+        }
+        return rowFieldGetterCache.computeIfAbsent(
+                rowType,
+                type -> {
+                    final RowData.FieldGetter[] getters =
+                            new RowData.FieldGetter[type.getFieldCount()];
+                    for (int i = 0; i < getters.length; i++) {
+                        getters[i] = 
RowData.createFieldGetter(type.getTypeAt(i), i);
+                    }
+                    return getters;
+                });
+    }
+
     /**
      * Reads the value from a VALUE-shaped state, dispatching to {@code 
ValueState.value()}, {@code
      * ReducingState.get()} or {@code AggregatingState.get()} depending on 
{@code actualStateKind}
@@ -193,6 +210,21 @@ class StateValueConverter implements java.io.Serializable {
                     }
                     return result;
                 }
+                if (object instanceof RowData) {
+                    final RowData sourceRow = (RowData) object;
+                    final RowType rowType = (RowType) logicalType;
+                    final RowData.FieldGetter[] getters = 
rowFieldGetters(rowType);
+                    final GenericRowData result =
+                            new GenericRowData(RowKind.INSERT, 
rowType.getFieldCount());
+                    for (int i = 0; i < rowType.getFieldCount(); i++) {
+                        result.setField(
+                                i,
+                                i < sourceRow.getArity()
+                                        ? getters[i].getFieldOrNull(sourceRow)
+                                        : null);
+                    }
+                    return result;
+                }
                 return convertToRow(object, logicalType);
             default:
                 return InternalTypeConverter.toInternal(object, logicalType);
diff --git 
a/flink-libraries/flink-state-processing-api/src/test/java/org/apache/flink/state/table/StateValueConverterTest.java
 
b/flink-libraries/flink-state-processing-api/src/test/java/org/apache/flink/state/table/StateValueConverterTest.java
new file mode 100644
index 00000000000..646923b2869
--- /dev/null
+++ 
b/flink-libraries/flink-state-processing-api/src/test/java/org/apache/flink/state/table/StateValueConverterTest.java
@@ -0,0 +1,113 @@
+/*
+ * 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.flink.state.table;
+
+import org.apache.flink.table.data.GenericRowData;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.data.StringData;
+import org.apache.flink.table.data.TimestampData;
+import org.apache.flink.table.data.binary.BinaryRowData;
+import org.apache.flink.table.runtime.typeutils.RowDataSerializer;
+import org.apache.flink.table.types.logical.BooleanType;
+import org.apache.flink.table.types.logical.IntType;
+import org.apache.flink.table.types.logical.RowType;
+import org.apache.flink.table.types.logical.TimestampType;
+import org.apache.flink.table.types.logical.VarCharType;
+import org.apache.flink.types.RowKind;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class StateValueConverterTest {
+
+    private final StateValueConverter converter = new StateValueConverter();
+
+    @Test
+    void convertsBinaryStateKey() {
+        final RowType type = RowType.of(new IntType(false));
+        final BinaryRowData source = new 
RowDataSerializer(type).toBinaryRow(GenericRowData.of(42));
+
+        final RowData result = (RowData) converter.getValue(type, source);
+
+        assertThat(result.getInt(0)).isEqualTo(42);
+        assertThat(result.getRowKind()).isEqualTo(RowKind.INSERT);
+    }
+
+    @Test
+    void convertsBinaryMapKey() {
+        final RowType type =
+                RowType.of(new IntType(false), new BooleanType(), new 
TimestampType(3));
+        final TimestampData timestamp = 
TimestampData.fromEpochMillis(123456789L);
+        final BinaryRowData source =
+                new RowDataSerializer(type).toBinaryRow(GenericRowData.of(7, 
true, timestamp));
+
+        final RowData result = (RowData) converter.getValue(type, source);
+
+        assertThat(result.getInt(0)).isEqualTo(7);
+        assertThat(result.getBoolean(1)).isTrue();
+        assertThat(result.getTimestamp(2, 3)).isEqualTo(timestamp);
+    }
+
+    @Test
+    void preservesNullBinaryFields() {
+        final RowType type = RowType.of(new IntType(), new VarCharType(), new 
TimestampType(3));
+        final BinaryRowData source =
+                new 
RowDataSerializer(type).toBinaryRow(GenericRowData.of(null, null, null));
+
+        final RowData result = (RowData) converter.getValue(type, source);
+
+        assertThat(result.isNullAt(0)).isTrue();
+        assertThat(result.isNullAt(1)).isTrue();
+        assertThat(result.isNullAt(2)).isTrue();
+    }
+
+    @Test
+    void readsNestedBinaryRows() {
+        final RowType nestedType = RowType.of(new IntType(), new 
VarCharType());
+        final RowType type = RowType.of(new IntType(), nestedType);
+        final BinaryRowData source =
+                new RowDataSerializer(type)
+                        .toBinaryRow(
+                                GenericRowData.of(
+                                        1, GenericRowData.of(2, 
StringData.fromString("nested"))));
+
+        final RowData result = (RowData) converter.getValue(type, source);
+        final RowData nested = result.getRow(1, 2);
+
+        assertThat(result.getInt(0)).isEqualTo(1);
+        assertThat(nested.getInt(0)).isEqualTo(2);
+        assertThat(nested.getString(1).toString()).isEqualTo("nested");
+    }
+
+    @Test
+    void preservesGenericRowNormalizationAndMissingFields() {
+        final RowType type = RowType.of(new IntType(), new VarCharType(), new 
BooleanType());
+        final GenericRowData source = GenericRowData.of(3, 
StringData.fromString("existing"));
+        source.setRowKind(RowKind.UPDATE_AFTER);
+
+        final RowData result = (RowData) converter.getValue(type, source);
+
+        assertThat(result).isNotSameAs(source);
+        assertThat(result.getRowKind()).isEqualTo(RowKind.INSERT);
+        assertThat(result.getInt(0)).isEqualTo(3);
+        assertThat(result.getString(1).toString()).isEqualTo("existing");
+        assertThat(result.isNullAt(2)).isTrue();
+    }
+}

Reply via email to