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

MartijnVisser 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 7a76f129672 [FLINK-40883][table] Fix accumulator corruption in 
NonTimeRowsUnboundedPrecedingFunction (#29373)
7a76f129672 is described below

commit 7a76f129672193aa170ba18c25090f8586fc54e4
Author: Bonnie Varghese <[email protected]>
AuthorDate: Wed Oct 7 13:08:48 2026 -0700

    [FLINK-40883][table] Fix accumulator corruption in 
NonTimeRowsUnboundedPrecedingFunction (#29373)
    
    - processRemainingElements and reAccumulateIdsAndEmitUpdates stored
      aggFuncs.getValue() into accMapState instead of
      aggFuncs.getAccumulators(), and emitted the raw stored accumulator as
      the "before" value. For aggregates whose accumulator differs from their
      result (e.g. LAST_VALUE with retraction), a recalculation after an
      out-of-order insert or a delete wrote a result-shaped row into the
      accumulator state, which fails on RocksDB when it is serialized
      (ArrayIndexOutOfBoundsException in RowDataSerializer).
    - Compare accumulator to accumulator for the early-out check, derive the
      before/after values via setAccumulatorAndGetValue(), and store the
      accumulator in accMapState, matching
      NonTimeRangeUnboundedPrecedingFunction.
    - When the early-out fires on the delete path, skip processing of the
      following sort keys.
    
    Generated-by: Claude Code (Claude Opus 5.5)
---
 .../NonTimeRowsUnboundedPrecedingFunction.java     |  43 ++++--
 .../over/LastValueAggsHandleFunction.java          |  96 ++++++++++++
 .../operators/over/NonTimeOverWindowTestBase.java  |  69 +++++++--
 ...NonTimeRangeUnboundedPrecedingFunctionTest.java | 114 ++++++++++----
 .../NonTimeRowsUnboundedPrecedingFunctionTest.java | 164 +++++++++++++++++----
 .../operators/over/RowTimeOverWindowTestBase.java  |  10 ++
 6 files changed, 413 insertions(+), 83 deletions(-)

diff --git 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunction.java
 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunction.java
index e5a1ca7e958..6d6f89760cc 100644
--- 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunction.java
+++ 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunction.java
@@ -273,11 +273,12 @@ public class NonTimeRowsUnboundedPrecedingFunction<K>
             // to comply with the sql rows syntax
             for (int j = 0; j < ids.size(); j++) {
                 RowData value = valueMapState.get(ids.get(j));
+                RowData prevAcc = 
accMapState.get(GenericRowData.of(ids.get(j)));
                 aggFuncs.accumulate(value);
-                RowData accData = 
accMapState.get(GenericRowData.of(ids.get(j)));
+                RowData newAcc = aggFuncs.getAccumulators();
                 // Logic to early out
                 // TODO: Move comparison to function i.e. canEarlyOut(prev, 
curr)
-                if (aggFuncs.getValue().equals(accData)) {
+                if (newAcc.equals(prevAcc)) {
                     // Previous accumulator is the same as the current 
accumulator.
                     // This means all the ids will have no change in the 
accumulated value.
                     // Skip sending downstream updates in such cases to reduce 
network traffic
@@ -285,9 +286,12 @@ public class NonTimeRowsUnboundedPrecedingFunction<K>
                             "Prev accumulator is same as curr accumulator. 
Skipping further updates.");
                     return;
                 }
-                collectUpdateBefore(out, value, accData);
-                collectUpdateAfter(out, value, aggFuncs.getValue());
-                accMapState.put(GenericRowData.of(ids.get(j)), 
aggFuncs.getValue());
+                RowData newValue = aggFuncs.getValue();
+                RowData prevValue = setAccumulatorAndGetValue(prevAcc);
+                aggFuncs.setAccumulators(newAcc);
+                collectUpdateBefore(out, value, prevValue);
+                collectUpdateAfter(out, value, newValue);
+                accMapState.put(GenericRowData.of(ids.get(j)), newAcc);
             }
         }
     }
@@ -335,7 +339,7 @@ public class NonTimeRowsUnboundedPrecedingFunction<K>
         aggFuncs.setAccumulators(prevAcc);
 
         // Re-accumulate ids and emit updates for ids
-        reAccumulateIdsAndEmitUpdates(ids, removeIndex, out);
+        boolean isEarlyOut = reAccumulateIdsAndEmitUpdates(ids, removeIndex, 
out);
 
         // Remove id and update sortedList
         Long deletedId = ids.remove(removeIndex);
@@ -346,6 +350,9 @@ public class NonTimeRowsUnboundedPrecedingFunction<K>
         accMapState.remove(GenericRowData.of(deletedId));
         sortedListState.update(sortedList);
 
+        if (isEarlyOut) {
+            return;
+        }
         processRemainingElements(sortedList, idxOfSortKey, 
aggFuncs.getAccumulators(), out);
     }
 
@@ -398,24 +405,34 @@ public class NonTimeRowsUnboundedPrecedingFunction<K>
      * @param ids
      * @param removeIndex
      * @param out
+     * @return true if an id's accumulator was unchanged, so all following ids 
are unchanged too
      * @throws Exception
      */
-    private void reAccumulateIdsAndEmitUpdates(
+    private boolean reAccumulateIdsAndEmitUpdates(
             List<Long> ids, int removeIndex, Collector<RowData> out) throws 
Exception {
+        RowData baseAcc = aggFuncs.getAccumulators();
         for (int j = removeIndex; j < ids.size(); j++) {
             RowData value = valueMapState.get(ids.get(j));
             if (j == removeIndex) {
-                collectDelete(out, value, 
accMapState.get(GenericRowData.of(ids.get(j))));
+                RowData deletedAcc = 
accMapState.get(GenericRowData.of(ids.get(j)));
+                collectDelete(out, value, 
setAccumulatorAndGetValue(deletedAcc));
+                aggFuncs.setAccumulators(baseAcc);
             } else {
+                RowData prevAcc = 
accMapState.get(GenericRowData.of(ids.get(j)));
                 aggFuncs.accumulate(value);
+                RowData newAcc = aggFuncs.getAccumulators();
                 // Logic to early out
-                if 
(aggFuncs.getValue().equals(accMapState.get(GenericRowData.of(ids.get(j))))) {
-                    break;
+                if (newAcc.equals(prevAcc)) {
+                    return true;
                 }
-                collectUpdateBefore(out, value, 
accMapState.get(GenericRowData.of(ids.get(j))));
-                collectUpdateAfter(out, value, aggFuncs.getValue());
-                accMapState.put(GenericRowData.of(ids.get(j)), 
aggFuncs.getValue());
+                RowData newValue = aggFuncs.getValue();
+                RowData prevValue = setAccumulatorAndGetValue(prevAcc);
+                aggFuncs.setAccumulators(newAcc);
+                collectUpdateBefore(out, value, prevValue);
+                collectUpdateAfter(out, value, newValue);
+                accMapState.put(GenericRowData.of(ids.get(j)), newAcc);
             }
         }
+        return false;
     }
 }
diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/LastValueAggsHandleFunction.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/LastValueAggsHandleFunction.java
new file mode 100644
index 00000000000..2df315d8a70
--- /dev/null
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/LastValueAggsHandleFunction.java
@@ -0,0 +1,96 @@
+/*
+ * 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.table.runtime.operators.over;
+
+import org.apache.flink.table.data.GenericRowData;
+import org.apache.flink.table.data.RowData;
+import org.apache.flink.table.runtime.dataview.StateDataViewStore;
+import org.apache.flink.table.runtime.generated.AggsHandleFunction;
+
+/**
+ * Test {@link AggsHandleFunction} whose accumulator shape, (lastValue, 
count), differs from its
+ * result shape, (lastValue) only - mirroring built-in aggregates like 
LAST_VALUE with retraction,
+ * where the accumulator carries bookkeeping fields beyond what getValue() 
returns.
+ */
+public class LastValueAggsHandleFunction implements AggsHandleFunction {
+
+    private final int inputIndex;
+    private Long lastValue;
+    private long count;
+
+    public LastValueAggsHandleFunction(int inputIndex) {
+        this.inputIndex = inputIndex;
+    }
+
+    @Override
+    public void open(StateDataViewStore store) throws Exception {}
+
+    @Override
+    public void accumulate(RowData input) throws Exception {
+        lastValue = input.getLong(inputIndex);
+        count++;
+    }
+
+    @Override
+    public void retract(RowData input) throws Exception {
+        count--;
+    }
+
+    @Override
+    public void merge(RowData accumulator) throws Exception {
+        lastValue = accumulator.getLong(0);
+        count += accumulator.getLong(1);
+    }
+
+    @Override
+    public void setAccumulators(RowData accumulator) throws Exception {
+        lastValue = accumulator.getLong(0);
+        count = accumulator.getLong(1);
+    }
+
+    @Override
+    public void resetAccumulators() throws Exception {
+        lastValue = null;
+        count = 0;
+    }
+
+    @Override
+    public RowData getAccumulators() throws Exception {
+        return GenericRowData.of(lastValue, count);
+    }
+
+    @Override
+    public RowData createAccumulators() throws Exception {
+        return GenericRowData.of(0L, 0L);
+    }
+
+    @Override
+    public RowData getValue() throws Exception {
+        return GenericRowData.of(lastValue);
+    }
+
+    @Override
+    public void setWindowSize(int windowSize) {}
+
+    @Override
+    public void cleanup() throws Exception {}
+
+    @Override
+    public void close() throws Exception {}
+}
diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeOverWindowTestBase.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeOverWindowTestBase.java
index 2c42980c389..ca557ae3a28 100644
--- 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeOverWindowTestBase.java
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeOverWindowTestBase.java
@@ -19,6 +19,10 @@
 package org.apache.flink.table.runtime.operators.over;
 
 import org.apache.flink.api.java.tuple.Tuple2;
+import org.apache.flink.runtime.state.hashmap.HashMapStateBackend;
+import org.apache.flink.state.rocksdb.EmbeddedRocksDBStateBackend;
+import org.apache.flink.streaming.api.operators.KeyedProcessOperator;
+import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
 import org.apache.flink.table.data.GenericRowData;
 import org.apache.flink.table.data.RowData;
 import org.apache.flink.table.data.StringData;
@@ -30,20 +34,37 @@ import 
org.apache.flink.table.runtime.generated.RecordComparator;
 import org.apache.flink.table.runtime.generated.RecordEqualiser;
 import org.apache.flink.table.runtime.keyselector.RowDataKeySelector;
 import org.apache.flink.table.runtime.typeutils.InternalTypeInfo;
+import 
org.apache.flink.table.runtime.util.StateParameterizedHarnessTestBase.StateBackendMode;
 import org.apache.flink.table.types.logical.BigIntType;
 import org.apache.flink.table.types.logical.LogicalType;
 import org.apache.flink.table.types.logical.VarCharType;
 import org.apache.flink.table.utils.HandwrittenSelectorUtil;
+import org.apache.flink.testutils.junit.extensions.parameterized.Parameter;
+import 
org.apache.flink.testutils.junit.extensions.parameterized.ParameterizedTestExtension;
+import org.apache.flink.testutils.junit.extensions.parameterized.Parameters;
 import org.apache.flink.types.RowKind;
 
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assumptions.assumeThat;
 
 /** Base class for non-time over window test. */
+@ExtendWith(ParameterizedTestExtension.class)
 public abstract class NonTimeOverWindowTestBase extends 
RowTimeOverWindowTestBase {
 
+    @Parameter private StateBackendMode stateBackendMode;
+
+    @Parameters(name = "StateBackend={0}")
+    public static Collection<StateBackendMode> parameters() {
+        return Arrays.asList(StateBackendMode.HEAP, StateBackendMode.ROCKSDB);
+    }
+
     private static final int SORT_KEY_IDX = 1;
 
     static final LogicalType[] SORT_KEY_TYPES = new LogicalType[] {new 
BigIntType()};
@@ -142,13 +163,11 @@ public abstract class NonTimeOverWindowTestBase extends 
RowTimeOverWindowTestBas
         public boolean equals(RowData row1, RowData row2) {
             if (row1 instanceof BinaryRowData && row2 instanceof 
BinaryRowData) {
                 return row1.equals(row2);
-            } else if (row1 instanceof GenericRowData && row2 instanceof 
GenericRowData) {
-                return row1.getString(0).equals(row2.getString(0))
-                        && row1.getLong(1) == row2.getLong(1)
-                        && row1.getLong(2) == row2.getLong(2);
-            } else {
-                throw new UnsupportedOperationException();
             }
+            // Rows read back from RocksDB are BinaryRowData while input rows 
are GenericRowData
+            return row1.getString(0).equals(row2.getString(0))
+                    && row1.getLong(1) == row2.getLong(1)
+                    && row1.getLong(2) == row2.getLong(2);
         }
     }
 
@@ -169,6 +188,33 @@ public abstract class NonTimeOverWindowTestBase extends 
RowTimeOverWindowTestBas
         }
     }
 
+    @Override
+    protected OneInputStreamOperatorTestHarness<RowData, RowData> 
createTestHarness(
+            KeyedProcessOperator<RowData, RowData, RowData> operator) throws 
Exception {
+        OneInputStreamOperatorTestHarness<RowData, RowData> testHarness =
+                super.createTestHarness(operator);
+        switch (stateBackendMode) {
+            case HEAP:
+                testHarness.setStateBackend(new HashMapStateBackend());
+                break;
+            case ROCKSDB:
+                testHarness.setStateBackend(new EmbeddedRocksDBStateBackend());
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown mode: " + 
stateBackendMode);
+        }
+        return testHarness;
+    }
+
+    /**
+     * The early-out compares a {@link GenericRowData} accumulator with the 
{@link BinaryRowData}
+     * read back from RocksDB, which never matches until FLINK-40735 compares 
them with a {@link
+     * RecordEqualiser}.
+     */
+    void assumeEarlyOutSupported() {
+        assumeThat(stateBackendMode).isEqualTo(StateBackendMode.HEAP);
+    }
+
     abstract void validateEntry(
             AbstractNonTimeUnboundedPrecedingOver<RowData> function, RowData 
record, int idOffset)
             throws Exception;
@@ -222,12 +268,11 @@ public abstract class NonTimeOverWindowTestBase extends 
RowTimeOverWindowTestBas
 
         if (isInsertion) {
             // Validate if record was successfully inserted in the 
valueMapState
-            assertThat(
-                            function.getRuntimeContext()
-                                    .getMapState(function.valueStateDescriptor)
-                                    .get(Long.MIN_VALUE + idOffset)
-                                    .toString())
-                    .isEqualTo(record.toString());
+            RowData storedRow =
+                    function.getRuntimeContext()
+                            .getMapState(function.valueStateDescriptor)
+                            .get(Long.MIN_VALUE + idOffset);
+            assertThat(new TestRowValueEqualiser().equals(storedRow, 
record)).isTrue();
         } else {
             // Validate if record was successfully removed from the 
valueMapState
             assertThat(
diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRangeUnboundedPrecedingFunctionTest.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRangeUnboundedPrecedingFunctionTest.java
index 7f7e45a705c..03c748fdf90 100644
--- 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRangeUnboundedPrecedingFunctionTest.java
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRangeUnboundedPrecedingFunctionTest.java
@@ -24,10 +24,11 @@ import 
org.apache.flink.streaming.api.operators.KeyedProcessOperator;
 import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
 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.runtime.generated.GeneratedRecordComparator;
 import org.apache.flink.types.RowKind;
 
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestTemplate;
 
 import java.time.Duration;
 import java.util.Arrays;
@@ -56,7 +57,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
                 SORT_KEY_SELECTOR) {};
     }
 
-    @Test
+    @TestTemplate
     void testInsertOnlyRecordsWithCustomSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -97,7 +98,61 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
+    void testInsertWithDuplicateSortKeyAndLastValueAgg() throws Exception {
+        KeyedProcessOperator<RowData, RowData, RowData> operator =
+                new KeyedProcessOperator<>(
+                        new NonTimeRangeUnboundedPrecedingFunction<>(
+                                0L,
+                                lastValueAggsHandleFunction,
+                                GENERATED_ROW_VALUE_EQUALISER,
+                                GENERATED_SORT_KEY_EQUALISER,
+                                GENERATED_SORT_KEY_COMPARATOR_ASC,
+                                lastValueAccTypes,
+                                inputFieldTypes,
+                                SORT_KEY_TYPES,
+                                SORT_KEY_SELECTOR) {});
+
+        OneInputStreamOperatorTestHarness<RowData, RowData> testHarness =
+                createTestHarness(operator);
+        testHarness.open();
+
+        testHarness.processElement(insertRecord("key1", 1L, 100L));
+        testHarness.processElement(insertRecord("key1", 2L, 200L));
+        testHarness.processElement(insertRecord("key1", 5L, 500L));
+        testHarness.processElement(insertRecord("key1", 6L, 600L));
+        testHarness.processElement(insertRecord("key1", 4L, 400L));
+        testHarness.processElement(insertRecord("key1", 5L, 503L));
+        testHarness.processElement(updateBeforeRecord("key1", 5L, 500L));
+
+        List<RowData> expectedRows =
+                Arrays.asList(
+                        outputRecord(RowKind.INSERT, "key1", 1L, 100L, 100L),
+                        outputRecord(RowKind.INSERT, "key1", 2L, 200L, 200L),
+                        outputRecord(RowKind.INSERT, "key1", 5L, 500L, 500L),
+                        outputRecord(RowKind.INSERT, "key1", 6L, 600L, 600L),
+                        outputRecord(RowKind.INSERT, "key1", 4L, 400L, 400L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 5L, 500L, 
500L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 5L, 500L, 
500L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 5L, 500L, 
500L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 5L, 500L, 
503L),
+                        outputRecord(RowKind.INSERT, "key1", 5L, 503L, 503L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.DELETE, "key1", 5L, 500L, 503L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 5L, 503L, 
503L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 5L, 503L, 
503L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L));
+
+        List<RowData> actualRows = testHarness.extractOutputValues();
+
+        validateRows(actualRows, expectedRows);
+    }
+
+    @TestTemplate
     void testInsertOnlyRecordsWithCustomSortKeyAndLongSumAgg() throws 
Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -146,7 +201,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testInsertOnlyRecordsWithDuplicateSortKeys() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -226,7 +281,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractingRecordsWithCustomSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -294,7 +349,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithFirstDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -344,7 +399,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithMiddleDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -394,7 +449,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithLastDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -444,7 +499,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithDescendingSort() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -500,8 +555,9 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithEarlyOut() throws Exception {
+        assumeEarlyOutSupported();
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
                         getNonTimeRangeUnboundedPrecedingFunction(
@@ -550,7 +606,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testInsertAndRetractAllWithStateValidation() throws Exception {
         NonTimeRangeUnboundedPrecedingFunction<RowData> function =
                 getNonTimeRangeUnboundedPrecedingFunction(0L, 
GENERATED_SORT_KEY_COMPARATOR_ASC);
@@ -562,31 +618,31 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 2, 3, 0, 1, 3, 4, true);
 
-        GenericRowData fifthRecord = GenericRowData.of("key1", 5L, 502L);
+        GenericRowData fifthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 502L);
         testHarness.processElement(insertRecord("key1", 5L, 502L));
         validateState(function, fifthRecord, 2, 3, 1, 2, 4, 5, true);
 
-        GenericRowData sixthRecord = GenericRowData.of("key1", 5L, 501L);
+        GenericRowData sixthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 501L);
         testHarness.processElement(insertRecord("key1", 5L, 501L));
         validateState(function, sixthRecord, 2, 3, 2, 3, 5, 6, true);
 
-        GenericRowData seventhRecord = GenericRowData.of("key1", 6L, 600L);
+        GenericRowData seventhRecord = 
GenericRowData.of(StringData.fromString("key1"), 6L, 600L);
         testHarness.processElement(insertRecord("key1", 6L, 600L));
         validateState(function, seventhRecord, 3, 4, 0, 1, 6, 7, true);
 
@@ -617,7 +673,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         assertThat(function.getNumOfIdsNotFound().getCount()).isZero();
     }
 
-    @Test
+    @TestTemplate
     void testInsertWithStateTTLExpiration() throws Exception {
         Duration stateTtlTime = Duration.ofMillis(10);
         NonTimeRangeUnboundedPrecedingFunction<RowData> function =
@@ -631,15 +687,15 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
@@ -651,7 +707,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         testHarness.setProcessingTime(stateTtlTime.toMillis() + 1);
 
         // After insertion of the following record, there should be only 1 
record in state
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 0, 1, 0, 1, 0, 1, true);
 
@@ -668,7 +724,7 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         assertThat(function.getNumOfIdsNotFound().getCount()).isZero();
     }
 
-    @Test
+    @TestTemplate
     void testInsertAndRetractWithStateTTLExpiration() throws Exception {
         Duration stateTtlTime = Duration.ofMillis(10);
         NonTimeRangeUnboundedPrecedingFunction<RowData> function =
@@ -682,23 +738,23 @@ class NonTimeRangeUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBa
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 2, 3, 0, 1, 3, 4, true);
 
-        GenericRowData fifthRecord = GenericRowData.of("key1", 5L, 502L);
+        GenericRowData fifthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 502L);
         testHarness.processElement(insertRecord("key1", 5L, 502L));
         validateState(function, fifthRecord, 2, 3, 1, 2, 4, 5, true);
 
diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunctionTest.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunctionTest.java
index 7452169f661..0e687e3b59a 100644
--- 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunctionTest.java
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/NonTimeRowsUnboundedPrecedingFunctionTest.java
@@ -24,15 +24,19 @@ import 
org.apache.flink.streaming.api.operators.KeyedProcessOperator;
 import org.apache.flink.streaming.util.OneInputStreamOperatorTestHarness;
 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.runtime.generated.AggsHandleFunction;
+import org.apache.flink.table.runtime.generated.GeneratedAggsHandleFunction;
 import org.apache.flink.table.runtime.generated.GeneratedRecordComparator;
 import org.apache.flink.types.RowKind;
 
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestTemplate;
 
 import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
 
+import static 
org.apache.flink.table.runtime.util.StreamRecordUtils.deleteRecord;
 import static 
org.apache.flink.table.runtime.util.StreamRecordUtils.insertRecord;
 import static 
org.apache.flink.table.runtime.util.StreamRecordUtils.updateAfterRecord;
 import static 
org.apache.flink.table.runtime.util.StreamRecordUtils.updateBeforeRecord;
@@ -55,7 +59,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
                 SORT_KEY_SELECTOR) {};
     }
 
-    @Test
+    @TestTemplate
     void testInsertOnlyRecordsWithCustomSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -96,7 +100,108 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
+    void testInsertWithDuplicateSortKeyAndLastValueAgg() throws Exception {
+        KeyedProcessOperator<RowData, RowData, RowData> operator =
+                new KeyedProcessOperator<>(
+                        new NonTimeRowsUnboundedPrecedingFunction<RowData>(
+                                0L,
+                                lastValueAggsHandleFunction,
+                                GENERATED_ROW_VALUE_EQUALISER,
+                                GENERATED_SORT_KEY_EQUALISER,
+                                GENERATED_SORT_KEY_COMPARATOR_ASC,
+                                lastValueAccTypes,
+                                inputFieldTypes,
+                                SORT_KEY_TYPES,
+                                SORT_KEY_SELECTOR) {});
+
+        OneInputStreamOperatorTestHarness<RowData, RowData> testHarness =
+                createTestHarness(operator);
+        testHarness.open();
+
+        testHarness.processElement(insertRecord("key1", 1L, 100L));
+        testHarness.processElement(insertRecord("key1", 2L, 200L));
+        testHarness.processElement(insertRecord("key1", 5L, 500L));
+        testHarness.processElement(insertRecord("key1", 6L, 600L));
+        testHarness.processElement(insertRecord("key1", 4L, 400L));
+        testHarness.processElement(insertRecord("key1", 5L, 503L));
+        testHarness.processElement(updateBeforeRecord("key1", 5L, 500L));
+
+        List<RowData> expectedRows =
+                Arrays.asList(
+                        outputRecord(RowKind.INSERT, "key1", 1L, 100L, 100L),
+                        outputRecord(RowKind.INSERT, "key1", 2L, 200L, 200L),
+                        outputRecord(RowKind.INSERT, "key1", 5L, 500L, 500L),
+                        outputRecord(RowKind.INSERT, "key1", 6L, 600L, 600L),
+                        outputRecord(RowKind.INSERT, "key1", 4L, 400L, 400L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 5L, 500L, 
500L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 5L, 500L, 
500L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.INSERT, "key1", 5L, 503L, 503L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.DELETE, "key1", 5L, 500L, 500L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 5L, 503L, 
503L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 5L, 503L, 
503L),
+                        outputRecord(RowKind.UPDATE_BEFORE, "key1", 6L, 600L, 
600L),
+                        outputRecord(RowKind.UPDATE_AFTER, "key1", 6L, 600L, 
600L));
+
+        List<RowData> actualRows = testHarness.extractOutputValues();
+
+        validateRows(actualRows, expectedRows);
+    }
+
+    @TestTemplate
+    void testRetractWithEarlyOutDoesNotRestartFollowingSortKeys() throws 
Exception {
+        assumeEarlyOutSupported();
+        // Sum ts rather than the sort key so rows sharing a sort key change 
the accumulator
+        // differently
+        GeneratedAggsHandleFunction sumOfTsAggsHandleFunction =
+                new GeneratedAggsHandleFunction("Function", "", new Object[0]) 
{
+                    @Override
+                    public AggsHandleFunction newInstance(ClassLoader 
classLoader) {
+                        return new SumAggsHandleFunction(2);
+                    }
+                };
+        KeyedProcessOperator<RowData, RowData, RowData> operator =
+                new KeyedProcessOperator<>(
+                        new NonTimeRowsUnboundedPrecedingFunction<RowData>(
+                                0L,
+                                sumOfTsAggsHandleFunction,
+                                GENERATED_ROW_VALUE_EQUALISER,
+                                GENERATED_SORT_KEY_EQUALISER,
+                                GENERATED_SORT_KEY_COMPARATOR_ASC,
+                                accTypes,
+                                inputFieldTypes,
+                                SORT_KEY_TYPES,
+                                SORT_KEY_SELECTOR) {});
+
+        OneInputStreamOperatorTestHarness<RowData, RowData> testHarness =
+                createTestHarness(operator);
+        testHarness.open();
+
+        testHarness.processElement(insertRecord("key1", 1L, 0L));
+        testHarness.processElement(insertRecord("key1", 1L, 5L));
+        testHarness.processElement(insertRecord("key1", 1L, 7L));
+        testHarness.processElement(insertRecord("key1", 2L, 1L));
+        // Removing a zero leaves (1, 5) unchanged, so nothing after it 
changes either
+        testHarness.processElement(deleteRecord("key1", 1L, 0L));
+
+        List<RowData> expectedRows =
+                Arrays.asList(
+                        outputRecord(RowKind.INSERT, "key1", 1L, 0L, 0L),
+                        outputRecord(RowKind.INSERT, "key1", 1L, 5L, 5L),
+                        outputRecord(RowKind.INSERT, "key1", 1L, 7L, 12L),
+                        outputRecord(RowKind.INSERT, "key1", 2L, 1L, 13L),
+                        outputRecord(RowKind.DELETE, "key1", 1L, 0L, 0L));
+
+        List<RowData> actualRows = testHarness.extractOutputValues();
+
+        validateRows(actualRows, expectedRows);
+    }
+
+    @TestTemplate
     void testInsertOnlyRecordsWithCustomSortKeyAndLongSumAgg() throws 
Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -145,7 +250,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testInsertOnlyRecordsWithDuplicateSortKeys() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -213,7 +318,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractingRecordsWithCustomSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -281,7 +386,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithFirstDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -323,7 +428,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithMiddleDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -363,7 +468,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithLastDuplicateSortKey() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -401,7 +506,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithDescendingSort() throws Exception {
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
@@ -455,8 +560,9 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testRetractWithEarlyOut() throws Exception {
+        assumeEarlyOutSupported();
         KeyedProcessOperator<RowData, RowData, RowData> operator =
                 new KeyedProcessOperator<>(
                         getNonTimeRowsUnboundedPrecedingFunction(
@@ -497,7 +603,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         validateRows(actualRows, expectedRows);
     }
 
-    @Test
+    @TestTemplate
     void testInsertAndRetractAllWithStateValidation() throws Exception {
         NonTimeRowsUnboundedPrecedingFunction<RowData> function =
                 getNonTimeRowsUnboundedPrecedingFunction(0L, 
GENERATED_SORT_KEY_COMPARATOR_ASC);
@@ -509,31 +615,31 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 2, 3, 0, 1, 3, 4, true);
 
-        GenericRowData fifthRecord = GenericRowData.of("key1", 5L, 502L);
+        GenericRowData fifthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 502L);
         testHarness.processElement(insertRecord("key1", 5L, 502L));
         validateState(function, fifthRecord, 2, 3, 1, 2, 4, 5, true);
 
-        GenericRowData sixthRecord = GenericRowData.of("key1", 5L, 501L);
+        GenericRowData sixthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 501L);
         testHarness.processElement(insertRecord("key1", 5L, 501L));
         validateState(function, sixthRecord, 2, 3, 2, 3, 5, 6, true);
 
-        GenericRowData seventhRecord = GenericRowData.of("key1", 6L, 600L);
+        GenericRowData seventhRecord = 
GenericRowData.of(StringData.fromString("key1"), 6L, 600L);
         testHarness.processElement(insertRecord("key1", 6L, 600L));
         validateState(function, seventhRecord, 3, 4, 0, 1, 6, 7, true);
 
@@ -564,7 +670,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         assertThat(function.getNumOfIdsNotFound().getCount()).isZero();
     }
 
-    @Test
+    @TestTemplate
     void testInsertWithStateTTLExpiration() throws Exception {
         Duration stateTtlTime = Duration.ofMillis(10);
         NonTimeRowsUnboundedPrecedingFunction<RowData> function =
@@ -578,15 +684,15 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
@@ -599,7 +705,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
 
         // After insertion of the following record, there should be only 1 
record in state due to
         // state ttl expiration
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 0, 1, 0, 1, 0, 1, true);
 
@@ -616,7 +722,7 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         assertThat(function.getNumOfIdsNotFound().getCount()).isZero();
     }
 
-    @Test
+    @TestTemplate
     void testInsertAndRetractWithStateTTLExpiration() throws Exception {
         Duration stateTtlTime = Duration.ofMillis(10);
         NonTimeRowsUnboundedPrecedingFunction<RowData> function =
@@ -630,23 +736,23 @@ class NonTimeRowsUnboundedPrecedingFunctionTest extends 
NonTimeOverWindowTestBas
         testHarness.open();
 
         // put some records
-        GenericRowData firstRecord = GenericRowData.of("key1", 1L, 100L);
+        GenericRowData firstRecord = 
GenericRowData.of(StringData.fromString("key1"), 1L, 100L);
         testHarness.processElement(insertRecord("key1", 1L, 100L));
         validateState(function, firstRecord, 0, 1, 0, 1, 0, 1, true);
 
-        GenericRowData secondRecord = GenericRowData.of("key1", 2L, 200L);
+        GenericRowData secondRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 200L);
         testHarness.processElement(insertRecord("key1", 2L, 200L));
         validateState(function, secondRecord, 1, 2, 0, 1, 1, 2, true);
 
-        GenericRowData thirdRecord = GenericRowData.of("key1", 2L, 201L);
+        GenericRowData thirdRecord = 
GenericRowData.of(StringData.fromString("key1"), 2L, 201L);
         testHarness.processElement(insertRecord("key1", 2L, 201L));
         validateState(function, thirdRecord, 1, 2, 1, 2, 2, 3, true);
 
-        GenericRowData fourthRecord = GenericRowData.of("key1", 5L, 500L);
+        GenericRowData fourthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 500L);
         testHarness.processElement(insertRecord("key1", 5L, 500L));
         validateState(function, fourthRecord, 2, 3, 0, 1, 3, 4, true);
 
-        GenericRowData fifthRecord = GenericRowData.of("key1", 5L, 502L);
+        GenericRowData fifthRecord = 
GenericRowData.of(StringData.fromString("key1"), 5L, 502L);
         testHarness.processElement(insertRecord("key1", 5L, 502L));
         validateState(function, fifthRecord, 2, 3, 1, 2, 4, 5, true);
 
diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/RowTimeOverWindowTestBase.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/RowTimeOverWindowTestBase.java
index 2ea90fa1ad9..48a558f878c 100644
--- 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/RowTimeOverWindowTestBase.java
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/operators/over/RowTimeOverWindowTestBase.java
@@ -49,9 +49,19 @@ public class RowTimeOverWindowTestBase {
                 }
             };
 
+    protected static GeneratedAggsHandleFunction lastValueAggsHandleFunction =
+            new GeneratedAggsHandleFunction("Function", "", new Object[0]) {
+                @Override
+                public AggsHandleFunction newInstance(ClassLoader classLoader) 
{
+                    return new LastValueAggsHandleFunction(2);
+                }
+            };
+
     protected LogicalType[] inputFieldTypes =
             new LogicalType[] {VarCharType.STRING_TYPE, new BigIntType(), new 
BigIntType()};
     protected LogicalType[] accTypes = new LogicalType[] {new BigIntType()};
+    protected LogicalType[] lastValueAccTypes =
+            new LogicalType[] {new BigIntType(), new BigIntType()};
 
     protected RowDataKeySelector keySelector =
             HandwrittenSelectorUtil.getRowDataSelector(new int[] {0}, 
inputFieldTypes);

Reply via email to