This is an automated email from the ASF dual-hosted git repository.
hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git
The following commit(s) were added to refs/heads/main by this push:
new 59a407581a fix RowMeta#addValueMeta, the cache used to check for
duplicate fields was cleared (#6013)
59a407581a is described below
commit 59a407581a4c4122c9c4b7cb735b8540d0814e5b
Author: lance <[email protected]>
AuthorDate: Mon Nov 17 23:12:49 2025 +0800
fix RowMeta#addValueMeta, the cache used to check for duplicate fields was
cleared (#6013)
Signed-off-by: lance <[email protected]>
---
.../main/java/org/apache/hop/core/row/RowMeta.java | 17 +++-
.../org/apache/hop/core/row/RowMetaCacheTest.java | 97 ++++++++++++++++++++++
2 files changed, 112 insertions(+), 2 deletions(-)
diff --git a/core/src/main/java/org/apache/hop/core/row/RowMeta.java
b/core/src/main/java/org/apache/hop/core/row/RowMeta.java
index 27645fa2b6..b99c3df310 100644
--- a/core/src/main/java/org/apache/hop/core/row/RowMeta.java
+++ b/core/src/main/java/org/apache/hop/core/row/RowMeta.java
@@ -272,7 +272,9 @@ public class RowMeta implements IRowMeta {
newMeta = renameValueMetaIfInRow(meta, null);
}
valueMetaList.add(index, newMeta);
- cache.invalidate();
+ // If data is inserted at the index position, the subsequent data will
be moved one step
+ // backwards.
+ cache.insertAtMapping(newMeta.getName(), index);
needRealClone = null;
} finally {
lock.writeLock().unlock();
@@ -1305,6 +1307,17 @@ public class RowMeta implements IRowMeta {
storeMapping(current, index);
}
+ void insertAtMapping(String name, int index) {
+ if (Utils.isEmpty(name) || index < 0) {
+ return;
+ }
+
+ String key = name.toLowerCase();
+ // For all values that are greater than or equal to the index, increment
them by 1.
+ mapping.replaceAll((k, v) -> v >= index ? v + 1 : v);
+ mapping.put(key, index);
+ }
+
Integer findAndCompare(String name, List<? extends IValueMeta> metas) {
if (Utils.isEmpty(name)) {
return null;
@@ -1314,7 +1327,7 @@ public class RowMeta implements IRowMeta {
Integer index = mapping.get(name);
if (index != null) {
IValueMeta value = metas.get(index);
- if (!name.equals(value.getName())) { // case insensitive since we
lowercase
+ if (!name.equalsIgnoreCase(value.getName())) {
mapping.remove(name);
index = null;
}
diff --git a/core/src/test/java/org/apache/hop/core/row/RowMetaCacheTest.java
b/core/src/test/java/org/apache/hop/core/row/RowMetaCacheTest.java
new file mode 100644
index 0000000000..fbca937f87
--- /dev/null
+++ b/core/src/test/java/org/apache/hop/core/row/RowMetaCacheTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.hop.core.row;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link org.apache.hop.core.row.RowMeta.RowMetaCache} */
+class RowMetaCacheTest {
+ private RowMeta.RowMetaCache cache;
+ private List<IValueMeta> metas;
+
+ @BeforeEach
+ void setUp() {
+ cache = new RowMeta.RowMetaCache();
+ metas = new ArrayList<>();
+ }
+
+ @Test
+ void testFindAndCompare() {
+ cache.storeMapping("Price", 3);
+ metas.addAll(List.of(meta("id"), meta("name"), meta("age"),
meta("price")));
+
+ Integer idx = cache.findAndCompare("PRICE", metas);
+ assertEquals(3, idx);
+ assertTrue(cache.mapping.containsKey("price"));
+ }
+
+ @Test
+ void testInsertAtBeginning() {
+ cache.storeMapping("id", 0);
+ cache.storeMapping("name", 1);
+
+ cache.insertAtMapping("sale", 0);
+ assertEquals(3, cache.mapping.size());
+
+ assertEquals(0, cache.mapping.get("sale"));
+ assertEquals(1, cache.mapping.get("id"));
+ assertEquals(2, cache.mapping.get("name"));
+ }
+
+ @Test
+ void testInsertAtMiddle() {
+ cache.storeMapping("id", 0);
+ cache.storeMapping("name", 1);
+ cache.storeMapping("age", 2);
+ cache.storeMapping("city", 3);
+
+ cache.insertAtMapping("sale", 2);
+ assertEquals(5, cache.mapping.size());
+
+ assertEquals(2, cache.mapping.get("sale"));
+ assertEquals(3, cache.mapping.get("age"));
+ assertEquals(4, cache.mapping.get("city"));
+ }
+
+ @Test
+ void testInsertAtEnd() {
+ cache.storeMapping("id", 0);
+ cache.storeMapping("name", 1);
+
+ cache.insertAtMapping("sale", 2);
+ assertEquals(3, cache.mapping.size());
+
+ assertEquals(0, cache.mapping.get("id"));
+ assertEquals(1, cache.mapping.get("name"));
+ assertEquals(2, cache.mapping.get("sale"));
+ }
+
+ private static IValueMeta meta(String name) {
+ IValueMeta vm = mock(IValueMeta.class);
+ when(vm.getName()).thenReturn(name);
+ return vm;
+ }
+}