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

mattcasters 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 3803f8fb6a fix performance regression in combination lookup/update , 
fixes #8922 (#8925)
3803f8fb6a is described below

commit 3803f8fb6a4c010edd69e8fb987a370991e1909b
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Wed Oct 14 23:31:31 2026 +0200

    fix performance regression in combination lookup/update , fixes #8922 
(#8925)
    
    and an extra performance bump for DimensionLookup
---
 .../combinationlookup/CombinationLookup.java       | 144 ++++++++++++-------
 .../combinationlookup/CombinationLookupData.java   |   4 +
 .../combinationlookup/CombinationLookupTest.java   | 159 +++++++++++++++++++++
 .../dimensionlookup/DimensionLookup.java           |  30 +++-
 .../DimensionLookupTechnicalKeyTest.java           |  22 +++
 5 files changed, 305 insertions(+), 54 deletions(-)

diff --git 
a/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookup.java
 
b/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookup.java
index eafbf4ee13..331d682c2c 100644
--- 
a/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookup.java
+++ 
b/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookup.java
@@ -87,7 +87,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
 
   private void determineTechKeyCreation() {
     String keyCreation = 
meta.getFields().getReturnFields().getTechKeyCreation();
-    DatabaseMeta databaseMeta = 
getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+    DatabaseMeta databaseMeta = data.db.getDatabaseMeta();
     if (databaseMeta.supportsAutoinc()
         && CombinationLookupMeta.CREATION_METHOD_AUTOINC.equals(keyCreation)) {
       setTechKeyCreation(CREATION_METHOD_AUTOINC);
@@ -99,15 +99,51 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
     }
   }
 
-  private Long lookupInCache(IRowMeta rowMeta, Object[] row) {
+  /**
+   * A cache key that carries the hash code of the key fields, so a lookup 
calculates it once for
+   * the hash field, the cache lookup and the cache store.
+   */
+  static final class CacheKey extends RowMetaAndData {
+    private final int hash;
+
+    CacheKey(IRowMeta rowMeta, Object[] row, int hash) {
+      super(rowMeta, row);
+      this.hash = hash;
+    }
+
+    @Override
+    public int hashCode() {
+      return hash;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+      // The key fields decide, the hash is only the hash code of those fields
+      return super.equals(obj);
+    }
+  }
+
+  /**
+   * The cache key of a row. The hash this transform calculates for the hash 
field is the same hash
+   * code, so it is reused. A hash read from the stream can be any number, so 
the cache does not use
+   * it: entries loaded from the table are keyed by the hash code of the key 
fields.
+   */
+  CacheKey cacheKey(Object[] hashRow, Long valHash) throws HopValueException {
+    if (valHash != null && data.hashFieldNr < 0) {
+      return new CacheKey(data.hashRowMeta, hashRow, valHash.intValue());
+    }
+    return new CacheKey(data.hashRowMeta, hashRow, 
data.hashRowMeta.hashCode(hashRow));
+  }
+
+  private Long lookupInCache(CacheKey key) {
     // Short circuit if cache is disabled.
-    if (meta.getCacheSize() == -1) {
+    if (key == null) {
       return null;
     }
 
     // try to find the row in the cache...
     //
-    return data.cache.get(new RowMetaAndData(rowMeta, row));
+    return data.cache.get(key);
   }
 
   /**
@@ -117,19 +153,18 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
    * <p>Key: - natural key fields Value: - Technical key - lookup fields / 
extra fields (allows us
    * to compare or retrieve) - Date_from - Date_to
    *
-   * @param rowMeta
-   * @param row
+   * @param cacheKey
    * @param tk
    * @throws HopValueException
    */
-  private void addToCache(IRowMeta rowMeta, Object[] row, Long tk) throws 
HopValueException {
+  private void addToCache(CacheKey cacheKey, Long tk) throws HopValueException 
{
     // Short circuit if cache is disabled.
-    if (meta.getCacheSize() == -1) {
+    if (cacheKey == null) {
       return;
     }
 
     // store it in the cache if needed.
-    data.cache.put(new RowMetaAndData(rowMeta, row), tk);
+    data.cache.put(cacheKey, tk);
 
     // check if the size is not too big...
     // Allow for a buffer overrun of 20% and then remove those 20% in one go.
@@ -196,7 +231,11 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
     }
 
     if (isRowLevel()) {
-      logRowlevel("Cache store: key=" + rowMeta.getString(row) + "    key=" + 
tk);
+      logRowlevel(
+          "Cache store: key="
+              + cacheKey.getRowMeta().getString(cacheKey.getData())
+              + "    key="
+              + tk);
     }
   }
 
@@ -204,54 +243,32 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
     return techKeyCreation == CREATION_METHOD_AUTOINC;
   }
 
-  private Object[] lookupValues(IRowMeta rowMeta, Object[] row) throws 
HopException {
+  Object[] lookupValues(IRowMeta rowMeta, Object[] row) throws HopException {
     Long valKey = null;
     Long valHash = null;
-    Object[] hashRow = null;
+    CacheKey cacheKey = null;
     CFields fields = meta.getFields();
     List<KeyField> keyFields = fields.getKeyFields();
     ReturnFields returnFields = fields.getReturnFields();
 
-    Object[] lookupRow = new Object[data.lookupRowMeta.size()];
-    int lookupIndex = 0;
-
     if (meta.isUseHash() || meta.getCacheSize() >= 0) {
-      hashRow = new Object[data.hashRowMeta.size()];
+      Object[] hashRow = new Object[data.hashRowMeta.size()];
       for (int i = 0; i < keyFields.size(); i++) {
         hashRow[i] = row[data.keynrs[i]];
       }
 
       if (meta.isUseHash()) {
         valHash = hashValue(rowMeta, row, hashRow);
-        lookupRow[lookupIndex] = valHash;
-        lookupIndex++;
       }
-    }
-
-    for (int i = 0; i < keyFields.size(); i++) {
-      // Determine the index of this Key Field in the row meta/data
-      int rowIndex = data.keynrs[i];
-      lookupRow[lookupIndex] = row[rowIndex]; // KEYi = ?
-      lookupIndex++;
-
-      DatabaseMeta databaseMeta =
-          getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
-      if (databaseMeta.requiresCastToVariousForIsNull()
-          && rowMeta.getValueMeta(rowIndex).getType() == 
IValueMeta.TYPE_STRING) {
-        lookupRow[lookupIndex] =
-            rowMeta.getValueMeta(rowIndex).isNull(row[rowIndex]) ? null : 
"NotNull"; // KEYi IS
-        // NULL or
-        // ? IS
-        // NULL
-      } else {
-        lookupRow[lookupIndex] = row[data.keynrs[i]]; // KEYi IS NULL or ? IS 
NULL
+      if (meta.getCacheSize() >= 0) {
+        cacheKey = cacheKey(hashRow, valHash);
       }
-      lookupIndex++;
     }
 
     // Before doing the actual lookup in the database, see if it's not in the 
cache...
-    valKey = lookupInCache(data.hashRowMeta, hashRow);
+    valKey = lookupInCache(cacheKey);
     if (valKey == null) {
+      Object[] lookupRow = lookupRow(rowMeta, row, valHash);
       data.db.setValues(data.lookupRowMeta, lookupRow, 
data.prepStatementLookup);
       Object[] add = data.db.getLookup(data.prepStatementLookup);
       incrementLinesInput();
@@ -293,7 +310,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
         }
 
         // Also store it in our Hashtable...
-        addToCache(data.hashRowMeta, hashRow, valKey);
+        addToCache(cacheKey, valKey);
       } else {
         // Entry already exists...
         //
@@ -301,7 +318,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
             data.db
                 .getReturnRowMeta()
                 .getInteger(add, 0); // Sometimes it's not an integer, believe 
it or not.
-        addToCache(data.hashRowMeta, hashRow, valKey);
+        addToCache(cacheKey, valKey);
       }
     }
 
@@ -329,6 +346,35 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
     return outputRow;
   }
 
+  /**
+   * The parameters of the lookup statement: the hash (if used), then for 
every key KEYi = ? and
+   * KEYi IS NULL AND ? IS NULL. Only needed when the cache misses.
+   */
+  private Object[] lookupRow(IRowMeta rowMeta, Object[] row, Long valHash)
+      throws HopValueException {
+    Object[] lookupRow = new Object[data.lookupRowMeta.size()];
+    int lookupIndex = 0;
+
+    if (meta.isUseHash()) {
+      lookupRow[lookupIndex++] = valHash;
+    }
+
+    for (int i = 0; i < data.keynrs.length; i++) {
+      // Determine the index of this Key Field in the row meta/data
+      int rowIndex = data.keynrs[i];
+      lookupRow[lookupIndex++] = row[rowIndex]; // KEYi = ?
+
+      if (data.nullCheckFlags[i]) {
+        // CAST(? AS VARCHAR(256)) IS NULL
+        lookupRow[lookupIndex++] =
+            rowMeta.getValueMeta(rowIndex).isNull(row[rowIndex]) ? null : 
"NotNull";
+      } else {
+        lookupRow[lookupIndex++] = row[rowIndex]; // ? IS NULL
+      }
+    }
+    return lookupRow;
+  }
+
   Long hashValue(IRowMeta rowMeta, Object[] row, Object[] hashRow) throws 
HopValueException {
     if (data.hashFieldNr >= 0) {
       return rowMeta.getInteger(row, data.hashFieldNr);
@@ -364,8 +410,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
       data.outputRowMeta = getInputRowMeta().clone();
       meta.getFields(data.outputRowMeta, getTransformName(), null, null, this, 
metadataProvider);
 
-      DatabaseMeta databaseMeta =
-          getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+      DatabaseMeta databaseMeta = data.db.getDatabaseMeta();
       data.schemaTable =
           databaseMeta.getQuotedSchemaTableCombination(
               this, data.realSchemaName, data.realTableName);
@@ -441,10 +486,11 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
   }
 
   public void setCombiLookup(IRowMeta inputRowMeta) throws 
HopDatabaseException {
-    DatabaseMeta databaseMeta = 
getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+    DatabaseMeta databaseMeta = data.db.getDatabaseMeta();
     CFields fields = meta.getFields();
     List<KeyField> keyFields = fields.getKeyFields();
     ReturnFields returnFields = fields.getReturnFields();
+    data.nullCheckFlags = new boolean[keyFields.size()];
 
     String sql = "";
     boolean comma;
@@ -489,6 +535,9 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
       sql += " IS NULL AND ";
       if (databaseMeta.requiresCastToVariousForIsNull()) {
         sql += "CAST(? AS VARCHAR(256)) IS NULL";
+        // A string key is bound as a null/not-null flag
+        data.nullCheckFlags[i] =
+            inputRowMeta.getValueMeta(data.keynrs[i]).getType() == 
IValueMeta.TYPE_STRING;
       } else {
         sql += "? IS NULL";
       }
@@ -518,7 +567,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
   public Long combiInsert(IRowMeta rowMeta, Object[] row, Long valKey, Long 
valCrc)
       throws HopDatabaseException {
     String debug = "Combination insert";
-    DatabaseMeta databaseMeta = 
getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+    DatabaseMeta databaseMeta = data.db.getDatabaseMeta();
     CFields fields = meta.getFields();
     List<KeyField> keyFields = fields.getKeyFields();
     ReturnFields returnFields = fields.getReturnFields();
@@ -789,8 +838,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
         throw new HopConfigException(
             BaseMessages.getString(PKG, 
CONST_COMBINATION_LOOKUP_LOG_UNEXPECTED_ERROR));
       }
-      DatabaseMeta databaseMeta =
-          getPipelineMeta().findDatabase(meta.getConnectionName(), variables);
+      DatabaseMeta databaseMeta = data.db.getDatabaseMeta();
       if (databaseMeta == null) {
         throw new HopConfigException(
             BaseMessages.getString(PKG, 
CONST_COMBINATION_LOOKUP_LOG_UNEXPECTED_ERROR));
@@ -853,7 +901,7 @@ public class CombinationLookup extends 
BaseTransform<CombinationLookupMeta, Comb
         // Assumes the technical key is at position 0 !!
         System.arraycopy(cacheRow, 1, hashRow, 0, hashRow.length);
         // Potential Cache Overflow is ahndled inside
-        addToCache(hashRowMeta, hashRow, (Long) cacheRow[0]);
+        addToCache(cacheKey(hashRow, null), (Long) cacheRow[0]);
         incrementLinesInput();
       }
     }
diff --git 
a/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupData.java
 
b/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupData.java
index 1c3e1a2313..5a44f083f0 100644
--- 
a/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupData.java
+++ 
b/plugins/transforms/combinationlookup/src/main/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupData.java
@@ -29,6 +29,10 @@ import org.apache.hop.pipeline.transform.ITransformData;
 public class CombinationLookupData extends BaseTransformData implements 
ITransformData {
   public Database db;
   public int[] keynrs; // nrs in row of the keys
+
+  /** Per key: bind a null/not-null flag for "? IS NULL", the database casts 
it to VARCHAR */
+  public boolean[] nullCheckFlags;
+
   public int hashFieldNr = -1;
 
   public Map<RowMetaAndData, Long> cache;
diff --git 
a/plugins/transforms/combinationlookup/src/test/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupTest.java
 
b/plugins/transforms/combinationlookup/src/test/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupTest.java
new file mode 100644
index 0000000000..002c901cfe
--- /dev/null
+++ 
b/plugins/transforms/combinationlookup/src/test/java/org/apache/hop/pipeline/transforms/combinationlookup/CombinationLookupTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.pipeline.transforms.combinationlookup;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.sql.PreparedStatement;
+import java.util.HashMap;
+import org.apache.hop.core.HopEnvironment;
+import org.apache.hop.core.RowMetaAndData;
+import org.apache.hop.core.database.Database;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.pipeline.PipelineMeta;
+import org.apache.hop.pipeline.transform.TransformMeta;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+
+class CombinationLookupTest {
+
+  private CombinationLookupMeta meta;
+  private CombinationLookupData data;
+  private PipelineMeta pipelineMeta;
+  private CombinationLookup transform;
+  private Database db;
+  private RowMeta rowMeta;
+
+  @BeforeAll
+  static void setUpEnvironment() throws Exception {
+    HopEnvironment.init();
+    PluginRegistry.init();
+  }
+
+  @BeforeEach
+  void setUp() {
+    meta = new CombinationLookupMeta();
+    meta.setConnectionName("dwh");
+    meta.getFields().getKeyFields().add(new KeyField("country", "country"));
+    meta.getFields().getKeyFields().add(new KeyField("status", "status"));
+    data = new CombinationLookupData();
+    TransformMeta transformMeta = new TransformMeta("lookup", meta);
+    pipelineMeta = spy(new PipelineMeta());
+    pipelineMeta.addTransform(transformMeta);
+    transform = new CombinationLookup(transformMeta, meta, data, 0, 
pipelineMeta, null);
+
+    rowMeta = new RowMeta();
+    rowMeta.addValueMeta(new ValueMetaString("country"));
+    rowMeta.addValueMeta(new ValueMetaString("status"));
+    rowMeta.addValueMeta(new ValueMetaInteger("hash"));
+
+    db = mock(Database.class);
+    when(db.getDatabaseMeta()).thenReturn(mock(DatabaseMeta.class));
+    data.db = db;
+    data.prepStatementLookup = mock(PreparedStatement.class);
+    data.keynrs = new int[] {0, 1};
+    data.hashRowMeta = new RowMeta();
+    data.hashRowMeta.addValueMeta(rowMeta.getValueMeta(0));
+    data.hashRowMeta.addValueMeta(rowMeta.getValueMeta(1));
+    data.outputRowMeta = rowMeta.clone();
+    data.outputRowMeta.addValueMeta(new ValueMetaInteger("tk"));
+    data.cache = new HashMap<>();
+  }
+
+  /** The connection was loaded from the metadata for every key of every row 
(B418, 200x). */
+  @Test
+  void cacheHitDoesNotLoadTheConnectionOrQueryTheDatabase() throws Exception {
+    data.cache.put(new RowMetaAndData(data.hashRowMeta, "BE", "open"), 7L);
+
+    for (int i = 0; i < 3; i++) {
+      Object[] output = transform.lookupValues(rowMeta, new Object[] {"BE", 
"open", 0L});
+      assertEquals(7L, output[3]);
+    }
+
+    verify(pipelineMeta, never()).findDatabase(anyString());
+    verify(pipelineMeta, never()).findDatabase(anyString(), any());
+    verify(pipelineMeta, never()).findDatabase(anyString(), any(), 
anyBoolean());
+    verify(db, never()).setValues(any(IRowMeta.class), any(Object[].class), 
any());
+    verify(db, never()).getLookup(any(PreparedStatement.class));
+  }
+
+  @Test
+  void cacheMissBindsTheLookupRowAndCachesTheKey() throws Exception {
+    data.lookupRowMeta = new RowMeta();
+    for (int i = 0; i < 4; i++) {
+      data.lookupRowMeta.addValueMeta(new ValueMetaString("lookup" + i));
+    }
+    data.nullCheckFlags = new boolean[] {true, false};
+    RowMeta returnRowMeta = new RowMeta();
+    returnRowMeta.addValueMeta(new ValueMetaInteger("tk"));
+    when(db.getLookup(any(PreparedStatement.class))).thenReturn(new Object[] 
{5L});
+    when(db.getReturnRowMeta()).thenReturn(returnRowMeta);
+
+    Object[] row = new Object[] {"BE", "open", 0L};
+    assertEquals(5L, transform.lookupValues(rowMeta, row)[3]);
+    assertEquals(5L, transform.lookupValues(rowMeta, row)[3]);
+
+    ArgumentCaptor<Object[]> lookupRow = 
ArgumentCaptor.forClass(Object[].class);
+    verify(db, times(1)).setValues(any(IRowMeta.class), lookupRow.capture(), 
any());
+    // KEYi = ? and the IS NULL check: a flag where the database casts it, 
else the value
+    assertArrayEquals(new Object[] {"BE", "NotNull", "open", "open"}, 
lookupRow.getValue());
+    verify(db, times(1)).getLookup(any(PreparedStatement.class));
+  }
+
+  @Test
+  void calculatedHashIsAlsoTheCacheHash() throws Exception {
+    meta.setUseHash(true);
+    data.cache.put(new RowMetaAndData(data.hashRowMeta.clone(), "BE", "open"), 
7L);
+    data.hashRowMeta = spy(data.hashRowMeta);
+
+    Object[] output = transform.lookupValues(rowMeta, new Object[] {"BE", 
"open", 0L});
+
+    assertEquals(7L, output[3]);
+    verify(data.hashRowMeta, times(1)).hashCode(any(Object[].class));
+  }
+
+  /** A hash read from the stream can be any number, the cache keeps the key 
fields' hash code. */
+  @Test
+  void streamHashIsNotTheCacheHash() throws Exception {
+    meta.setUseHash(true);
+    data.hashFieldNr = 2;
+    data.cache.put(new RowMetaAndData(data.hashRowMeta, "BE", "open"), 7L);
+
+    Object[] output = transform.lookupValues(rowMeta, new Object[] {"BE", 
"open", 42L});
+
+    assertEquals(7L, output[3]);
+    verify(db, never()).getLookup(any(PreparedStatement.class));
+  }
+}
diff --git 
a/plugins/transforms/dimensionlookup/src/main/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookup.java
 
b/plugins/transforms/dimensionlookup/src/main/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookup.java
index 032338ab4a..e8cd9f8925 100644
--- 
a/plugins/transforms/dimensionlookup/src/main/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookup.java
+++ 
b/plugins/transforms/dimensionlookup/src/main/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookup.java
@@ -24,6 +24,7 @@ import static 
org.apache.hop.pipeline.transforms.dimensionlookup.DimensionLookup
 import static 
org.apache.hop.pipeline.transforms.dimensionlookup.DimensionLookupMeta.StartDateAlternative.NULL;
 import static 
org.apache.hop.pipeline.transforms.dimensionlookup.DimensionLookupMeta.StartDateAlternative.SYSTEM_DATE;
 
+import java.math.BigDecimal;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.util.ArrayList;
@@ -74,6 +75,9 @@ public class DimensionLookup extends 
BaseTransform<DimensionLookupMeta, Dimensio
 
   int[] columnLookupArray = null;
 
+  /** Describes the lookup date for the conversion to the cache's date-from 
type */
+  private final IValueMeta lookupDateMeta = new ValueMetaDate("lookupDate");
+
   public DimensionLookup(
       TransformMeta transformMeta,
       DimensionLookupMeta meta,
@@ -499,7 +503,7 @@ public class DimensionLookup extends 
BaseTransform<DimensionLookupMeta, Dimensio
    * used to throw, and it should not take a database to find that out again.
    */
   static Object asType(IValueMeta targetMeta, Object value) throws 
HopValueException {
-    if (value == null || targetMeta == null) {
+    if (value == null || targetMeta == null || 
isNativeValueOf(targetMeta.getType(), value)) {
       return value;
     }
     IValueMeta actualMeta = ValueMetaFactory.guessValueMetaInterface(value);
@@ -509,7 +513,22 @@ public class DimensionLookup extends 
BaseTransform<DimensionLookupMeta, Dimensio
     return targetMeta.convertData(actualMeta, value);
   }
 
-  private synchronized Object[] lookupValues(IRowMeta rowMeta, Object[] row) 
throws HopException {
+  /**
+   * Whether the value already is what the type stores, decided by what the 
value is. It runs for
+   * every row, so it creates no value metadata: that is expensive to 
construct.
+   */
+  private static boolean isNativeValueOf(int type, Object value) {
+    return switch (type) {
+      case IValueMeta.TYPE_INTEGER -> value instanceof Long;
+      case IValueMeta.TYPE_BIGNUMBER -> value instanceof BigDecimal;
+      case IValueMeta.TYPE_STRING -> value instanceof String;
+      case IValueMeta.TYPE_NUMBER -> value instanceof Double;
+      case IValueMeta.TYPE_BINARY -> value instanceof byte[];
+      default -> false;
+    };
+  }
+
+  private Object[] lookupValues(IRowMeta rowMeta, Object[] row) throws 
HopException {
     DLFields f = meta.getFields();
 
     // Determine the lookup date ("now") if we have a field that carries said
@@ -565,9 +584,8 @@ public class DimensionLookup extends 
BaseTransform<DimensionLookupMeta, Dimensio
       //
       IValueMeta preloadFromDateMeta =
           
data.preloadCache.getRowMeta().getValueMeta(data.preloadFromDateIndex);
-      IValueMeta valueDateMeta = new ValueMetaDate("lookupDate");
       lookupRow[data.preloadFromDateIndex] =
-          preloadFromDateMeta.convertData(valueDateMeta, valueDate);
+          preloadFromDateMeta.convertData(lookupDateMeta, valueDate);
 
       // Look up the row in the pre-load cache...
       //
@@ -747,8 +765,8 @@ public class DimensionLookup extends 
BaseTransform<DimensionLookupMeta, Dimensio
         valueVersion = data.returnRowMeta.getInteger(returnRow, 1);
 
         // Date range: ]-oo,+oo[
-        valueDateFrom = meta.getMinDate();
-        valueDateTo = meta.getMaxDate();
+        valueDateFrom = data.minDate;
+        valueDateTo = data.maxDate;
 
         // The other values, we compare with
         int cmp;
diff --git 
a/plugins/transforms/dimensionlookup/src/test/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookupTechnicalKeyTest.java
 
b/plugins/transforms/dimensionlookup/src/test/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookupTechnicalKeyTest.java
index 21d4f0a511..30c840ac07 100644
--- 
a/plugins/transforms/dimensionlookup/src/test/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookupTechnicalKeyTest.java
+++ 
b/plugins/transforms/dimensionlookup/src/test/java/org/apache/hop/pipeline/transforms/dimensionlookup/DimensionLookupTechnicalKeyTest.java
@@ -21,15 +21,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.CALLS_REAL_METHODS;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.never;
 
 import java.math.BigDecimal;
 import org.apache.hop.core.exception.HopValueException;
 import org.apache.hop.core.row.IValueMeta;
 import org.apache.hop.core.row.value.ValueMetaBigNumber;
 import org.apache.hop.core.row.value.ValueMetaBinary;
+import org.apache.hop.core.row.value.ValueMetaFactory;
 import org.apache.hop.core.row.value.ValueMetaInteger;
 import org.apache.hop.core.row.value.ValueMetaString;
 import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
 
 /**
  * The technical key travels between two descriptions that need not agree: the 
key column decides
@@ -62,6 +68,22 @@ class DimensionLookupTechnicalKeyTest {
     assertEquals(42L, handedOn);
   }
 
+  /**
+   * This runs for every output row. Guessing the value's metadata constructs 
a value meta, which is
+   * expensive (B436), so a value that already has the declared type must not 
need one.
+   */
+  @Test
+  void aValueThatAlreadyMatchesCreatesNoValueMetadata() throws 
HopValueException {
+    try (MockedStatic<ValueMetaFactory> factory =
+        mockStatic(ValueMetaFactory.class, CALLS_REAL_METHODS)) {
+      DimensionLookup.asType(new ValueMetaInteger("dimension_id"), 42L);
+      DimensionLookup.asType(new ValueMetaBigNumber("dimension_id", 38, 0), 
BigDecimal.ONE);
+      DimensionLookup.asType(new ValueMetaString("dimension_id", 36, 0), 
"a-uuid");
+
+      factory.verify(() -> ValueMetaFactory.guessValueMetaInterface(any()), 
never());
+    }
+  }
+
   @Test
   void aValueThatAlreadyMatchesIsPassedThroughUntouched() throws 
HopValueException {
     Long key = 42L;

Reply via email to