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 3e21c0cd34 Fix Database Lookup return type inference when Type is
empty (#7574)
3e21c0cd34 is described below
commit 3e21c0cd346002ae98b909f9b60cee7312fd3640
Author: Lance <[email protected]>
AuthorDate: Mon Jul 20 18:52:36 2026 +0800
Fix Database Lookup return type inference when Type is empty (#7574)
Signed-off-by: lance <[email protected]>
---
.../transforms/databaselookup/DatabaseLookup.java | 20 +-
.../databaselookup/DatabaseLookupData.java | 4 +
.../databaselookup/DatabaseLookupMeta.java | 58 ++--
.../messages/messages_en_US.properties | 2 +
.../databaselookup/DatabaseLookupDataTest.java | 80 ++++++
.../databaselookup/DatabaseLookupMetaTest.java | 268 ++++++++++++++++++-
.../databaselookup/DatabaseLookupUTest.java | 297 ++++++++++++++++++++-
.../databaselookup/DefaultCacheTest.java | 256 ++++++++++++++++++
.../transforms/databaselookup/KeyFieldTest.java | 80 ++++++
.../transforms/databaselookup/LookupTest.java | 95 +++++++
.../transforms/databaselookup/ReturnValueTest.java | 98 +++++++
.../databaselookup/readallcache/IndexTestBase.java | 101 +++++--
.../readallcache/ReadAllCacheTest.java | 9 +
.../readallcache/SearchingContextTest.java | 28 ++
14 files changed, 1334 insertions(+), 62 deletions(-)
diff --git
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
index 11152f7788..7ff1682500 100644
---
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
+++
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookup.java
@@ -223,6 +223,7 @@ public class DatabaseLookup extends
BaseTransform<DatabaseLookupMeta, DatabaseLo
this, meta.getSchemaName(), meta.getTableName());
IRowMeta fields = data.db.getTableFields(schemaTable);
+ data.dbRowMeta = fields;
if (fields != null) {
// Fill in the types...
for (int i = 0; i < keyFields.size(); i++) {
@@ -300,9 +301,9 @@ public class DatabaseLookup extends
BaseTransform<DatabaseLookupMeta, DatabaseLo
data.returnValueTypes = new int[returnValues.size()];
for (int i = 0; i < returnValues.size(); i++) {
- data.returnValueTypes[i] =
-
ValueMetaFactory.getIdForValueMeta(returnValues.get(i).getDefaultType());
IValueMeta v = data.outputRowMeta.getValueMeta(getInputRowMeta().size()
+ i).clone();
+ // Use resolved output type (may be inferred from the table when default
type is empty)
+ data.returnValueTypes[i] = v.getType();
data.returnMeta.addValueMeta(v);
}
}
@@ -318,10 +319,6 @@ public class DatabaseLookup extends
BaseTransform<DatabaseLookupMeta, DatabaseLo
if (first) {
first = false;
- // create the output metadata
- data.outputRowMeta = getInputRowMeta().clone();
- meta.getFields(data.outputRowMeta, getTransformName(), null, null, this,
metadataProvider);
-
Lookup lookup = meta.getLookup();
List<KeyField> keyFields = lookup.getKeyFields();
List<ReturnValue> returnValues = lookup.getReturnValues();
@@ -406,8 +403,19 @@ public class DatabaseLookup extends
BaseTransform<DatabaseLookupMeta, DatabaseLo
data.cache = DefaultCache.newCache(data, meta.getCacheSize());
}
+ // Query table metadata once: key types + return type inference for
empty default types
determineFieldsTypesQueryingDb();
+ // create the output metadata (pass table fields so empty return types
can be inferred)
+ data.outputRowMeta = getInputRowMeta().clone();
+ meta.getFields(
+ data.outputRowMeta,
+ getTransformName(),
+ new IRowMeta[] {data.dbRowMeta},
+ null,
+ this,
+ metadataProvider);
+
initNullIf();
initLookupMeta();
diff --git
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupData.java
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupData.java
index cfccb998c1..25e1c56a17 100644
---
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupData.java
+++
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupData.java
@@ -37,6 +37,10 @@ public class DatabaseLookupData extends BaseTransformData
implements ITransformD
public IRowMeta outputRowMeta;
public IRowMeta lookupMeta;
public IRowMeta returnMeta;
+
+ /** Cached lookup-table field metadata (from getTableFields), reused for
return type inference. */
+ public IRowMeta dbRowMeta;
+
public boolean isCanceled;
public boolean allEquals;
public int[] conditions;
diff --git
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMeta.java
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMeta.java
index 83dca66a1e..d0ba4ee967 100644
---
a/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMeta.java
+++
b/plugins/transforms/databaselookup/src/main/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMeta.java
@@ -130,34 +130,48 @@ public class DatabaseLookupMeta extends
BaseTransformMeta<DatabaseLookup, Databa
IHopMetadataProvider metadataProvider)
throws HopTransformException {
try {
- // info row metadata: null or length 0 : no lookup row metadata from
database
+ // Prefer an explicit return type. When none is configured, infer from
table field
+ // metadata (info row from design-time / runtime, otherwise query the
table).
//
- if (Utils.isEmpty(infoRowMeta) || infoRowMeta[0] == null) {
- for (ReturnValue returnValue : lookup.getReturnValues()) {
- IValueMeta v =
- ValueMetaFactory.createValueMeta(
- !Utils.isEmpty(returnValue.getNewName())
- ? returnValue.getNewName()
- : returnValue.getTableField(),
-
ValueMetaFactory.getIdForValueMeta(returnValue.getDefaultType()));
- v.setOrigin(name);
- row.addValueMeta(v);
- }
- return;
+ IRowMeta tableFields = null;
+ if (!Utils.isEmpty(infoRowMeta) && infoRowMeta[0] != null) {
+ tableFields = infoRowMeta[0];
}
for (ReturnValue returnValue : lookup.getReturnValues()) {
- IValueMeta v =
infoRowMeta[0].searchValueMeta(returnValue.getTableField());
- if (v != null) {
- IValueMeta copy = v.clone(); // avoid renaming other value meta
- copy.setName(
- !Utils.isEmpty(returnValue.getNewName())
- ? returnValue.getNewName()
- : returnValue.getTableField());
- copy.setOrigin(name);
- row.addValueMeta(copy);
+ String fieldName =
+ !Utils.isEmpty(returnValue.getNewName())
+ ? returnValue.getNewName()
+ : returnValue.getTableField();
+ int typeId =
ValueMetaFactory.getIdForValueMeta(returnValue.getDefaultType());
+ IValueMeta v;
+ if (typeId != IValueMeta.TYPE_NONE) {
+ v = ValueMetaFactory.createValueMeta(fieldName, typeId);
+ } else {
+ if (tableFields == null) {
+ tableFields = getTableFields(variables);
+ }
+ if (tableFields == null) {
+ throw new HopTransformException(
+ BaseMessages.getString(
+ PKG,
"DatabaseLookupMeta.Exception.UnableToRetrieveDataTypeOfReturnField"));
+ }
+ IValueMeta source =
tableFields.searchValueMeta(returnValue.getTableField());
+ if (source == null) {
+ throw new HopTransformException(
+ BaseMessages.getString(
+ PKG,
+ "DatabaseLookupMeta.Exception.UnableToFindReturnField",
+ returnValue.getTableField()));
+ }
+ v = source.clone(); // avoid renaming other value meta
+ v.setName(fieldName);
}
+ v.setOrigin(name);
+ row.addValueMeta(v);
}
+ } catch (HopTransformException e) {
+ throw e;
} catch (HopException e) {
throw new HopTransformException("Error getting fields metadata", e);
}
diff --git
a/plugins/transforms/databaselookup/src/main/resources/org/apache/hop/pipeline/transforms/databaselookup/messages/messages_en_US.properties
b/plugins/transforms/databaselookup/src/main/resources/org/apache/hop/pipeline/transforms/databaselookup/messages/messages_en_US.properties
index 996582ad25..935f6446de 100644
---
a/plugins/transforms/databaselookup/src/main/resources/org/apache/hop/pipeline/transforms/databaselookup/messages/messages_en_US.properties
+++
b/plugins/transforms/databaselookup/src/main/resources/org/apache/hop/pipeline/transforms/databaselookup/messages/messages_en_US.properties
@@ -97,6 +97,8 @@
DatabaseLookupMeta.Check.MissingReturnFieldsInLookupTable=Missing return fields
DatabaseLookupMeta.Check.NoInputReceivedFromOtherTransforms=No input received
from other transforms\!
DatabaseLookupMeta.Check.TransformIsReceivingInfoFromOtherTransforms=Transform
is receiving info from other transforms.
DatabaseLookupMeta.ERROR0004.ErrorGettingTableFields=An error occurred\:
+DatabaseLookupMeta.Exception.UnableToFindReturnField=Unable to find return
field [{0}] in the lookup table.
+DatabaseLookupMeta.Exception.UnableToRetrieveDataTypeOfReturnField=Unable to
retrieve data type of return fields because table metadata is not available
DatabaseLookupMeta.Impact.Key=Key
DatabaseLookupMeta.Impact.ReturnValue=Return value
DatabaseLookupMeta.Injection.Cache=Cache lookup data?
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupDataTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupDataTest.java
new file mode 100644
index 0000000000..6cd9cbf1de
--- /dev/null
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupDataTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.databaselookup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.row.RowMeta;
+import org.junit.jupiter.api.Test;
+
+class DatabaseLookupDataTest {
+
+ @Test
+ void defaultConstructor_InitializesNullRuntimeState() {
+ DatabaseLookupData data = new DatabaseLookupData();
+
+ assertNull(data.db);
+ assertNull(data.cache);
+ assertNull(data.nullif);
+ assertNull(data.keynrs);
+ assertNull(data.keynrs2);
+ assertNull(data.keytypes);
+ assertNull(data.outputRowMeta);
+ assertNull(data.lookupMeta);
+ assertNull(data.returnMeta);
+ assertNull(data.dbRowMeta);
+ assertNull(data.conditions);
+ assertNull(data.returnValueTypes);
+ assertNull(data.returnTrimTypes);
+ assertNull(data.trimIndexes);
+ assertFalse(data.isCanceled);
+ assertFalse(data.allEquals);
+ assertFalse(data.hasDBCondition);
+ }
+
+ @Test
+ void fields_CanBeAssignedAndRead() {
+ DatabaseLookupData data = new DatabaseLookupData();
+ data.dbRowMeta = new RowMeta();
+ data.lookupMeta = new RowMeta();
+ data.returnMeta = new RowMeta();
+ data.outputRowMeta = new RowMeta();
+ data.keynrs = new int[] {0};
+ data.keynrs2 = new int[] {-1};
+ data.keytypes = new int[] {1};
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_EQ};
+ data.returnValueTypes = new int[] {2};
+ data.returnTrimTypes = new String[] {"none"};
+ data.nullif = new Object[] {"N/A"};
+ data.cache = DefaultCache.newCache(data, 8);
+ data.allEquals = true;
+ data.hasDBCondition = true;
+ data.isCanceled = true;
+
+ assertTrue(data.allEquals);
+ assertTrue(data.hasDBCondition);
+ assertTrue(data.isCanceled);
+ assertEquals(0, data.keynrs[0]);
+ assertEquals(DatabaseLookupMeta.CONDITION_EQ, data.conditions[0]);
+ assertInstanceOf(DefaultCache.class, data.cache);
+ }
+}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMetaTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMetaTest.java
index c3cd31453e..5881268160 100644
---
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMetaTest.java
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupMetaTest.java
@@ -20,7 +20,10 @@ package org.apache.hop.pipeline.transforms.databaselookup;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
@@ -30,6 +33,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.hop.core.HopClientEnvironment;
+import org.apache.hop.core.ICheckResult;
import org.apache.hop.core.RowMetaAndData;
import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.exception.HopTransformException;
@@ -39,11 +43,15 @@ import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowMeta;
import org.apache.hop.core.row.RowMetaBuilder;
+import org.apache.hop.core.row.value.ValueMetaInteger;
import org.apache.hop.core.row.value.ValueMetaString;
+import org.apache.hop.core.variables.Variables;
import org.apache.hop.core.xml.XmlHandler;
import org.apache.hop.metadata.api.IHopMetadataProvider;
import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
import org.apache.hop.metadata.serializer.xml.XmlMetadataUtil;
+import org.apache.hop.pipeline.DatabaseImpact;
+import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.transform.TransformMeta;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -112,14 +120,122 @@ class DatabaseLookupMetaTest {
List<IValueMeta> expectedRow =
Arrays.asList(
new IValueMeta[] {
- new ValueMetaString("value"), new ValueMetaString("v1"), new
ValueMetaString("v2"),
+ new ValueMetaString("value"),
+ new ValueMetaString("v1"),
+ new ValueMetaString("v2"),
+ new ValueMetaString("v3"),
});
- assertEquals(3, row.getValueMetaList().size());
- for (int i = 0; i < 3; i++) {
+ assertEquals(4, row.getValueMetaList().size());
+ for (int i = 0; i < 4; i++) {
assertEquals(expectedRow.get(i).getName(),
row.getValueMetaList().get(i).getName());
}
}
+ @Test
+ void getFieldsInfersTypeFromInfoWhenDefaultTypeEmpty() throws Exception {
+ Lookup lookup = databaseLookupMeta.getLookup();
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "stock_name",
+ "",
+ "",
+ "",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ IRowMeta[] info = new IRowMeta[1];
+ info[0] = new RowMeta();
+ info[0].addValueMeta(new ValueMetaString("stock_name"));
+
+ IRowMeta row = new RowMeta();
+ row.addValueMeta(new ValueMetaString("id"));
+
+ databaseLookupMeta.getFields(row, "Database lookup", info, null, null,
null);
+
+ assertEquals(2, row.size());
+ IValueMeta stockName = row.searchValueMeta("stock_name");
+ assertNotNull(stockName);
+ assertEquals(IValueMeta.TYPE_STRING, stockName.getType());
+ }
+
+ @Test
+ void getFieldsInfersTypeFromInfoWhenDefaultTypeNone() throws Exception {
+ Lookup lookup = databaseLookupMeta.getLookup();
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "amount",
+ "amt",
+ "",
+ "None",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ IRowMeta[] info = new IRowMeta[1];
+ info[0] = new RowMeta();
+ info[0].addValueMeta(new ValueMetaInteger("amount"));
+
+ IRowMeta row = new RowMeta();
+ databaseLookupMeta.getFields(row, "Database lookup", info, null, null,
null);
+
+ assertEquals(1, row.size());
+ IValueMeta amt = row.searchValueMeta("amt");
+ assertNotNull(amt);
+ assertEquals(IValueMeta.TYPE_INTEGER, amt.getType());
+ }
+
+ @Test
+ void getFieldsExplicitTypeOverridesInfoType() throws Exception {
+ Lookup lookup = databaseLookupMeta.getLookup();
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "amount",
+ "",
+ "",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ IRowMeta[] info = new IRowMeta[1];
+ info[0] = new RowMeta();
+ info[0].addValueMeta(new ValueMetaInteger("amount"));
+
+ IRowMeta row = new RowMeta();
+ databaseLookupMeta.getFields(row, "Database lookup", info, null, null,
null);
+
+ assertEquals(1, row.size());
+ IValueMeta amount = row.searchValueMeta("amount");
+ assertNotNull(amount);
+ assertEquals(IValueMeta.TYPE_STRING, amount.getType());
+ }
+
+ @Test
+ void getFieldsThrowsWhenTypeMissingAndFieldNotInInfo() {
+ Lookup lookup = databaseLookupMeta.getLookup();
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "missing_col",
+ "",
+ "",
+ "",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ IRowMeta[] info = new IRowMeta[1];
+ info[0] = new RowMeta();
+ info[0].addValueMeta(new ValueMetaString("other"));
+
+ IRowMeta row = new RowMeta();
+ HopTransformException thrown =
+ assertThrows(
+ HopTransformException.class,
+ () -> databaseLookupMeta.getFields(row, "Database lookup", info,
null, null, null));
+ assertTrue(thrown.getMessage().contains("missing_col"));
+ }
+
@Test
void cloneTest() throws Exception {
DatabaseLookupMeta meta = new DatabaseLookupMeta();
@@ -358,4 +474,150 @@ class DatabaseLookupMetaTest {
assertEquals("?",
meta.getLookup().getReturnValues().get(0).getDefaultValue());
assertEquals("String",
meta.getLookup().getReturnValues().get(0).getDefaultType());
}
+
+ @Test
+ void supportsErrorHandling_ReturnsTrue() {
+ assertTrue(databaseLookupMeta.supportsErrorHandling());
+ }
+
+ @Test
+ void getConditionStrings_ContainsAllOperators() {
+ List<String> conditions = DatabaseLookupMeta.getConditionStrings();
+ assertEquals(10, conditions.size());
+ assertTrue(conditions.contains("="));
+ assertTrue(conditions.contains("<>"));
+ assertTrue(conditions.contains("LIKE"));
+ assertTrue(conditions.contains("BETWEEN"));
+ assertTrue(conditions.contains("IS NULL"));
+ assertTrue(conditions.contains("IS NOT NULL"));
+ }
+
+ @Test
+ void check_ReportsErrorWhenConnectionMissing() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("");
+ List<ICheckResult> remarks = new ArrayList<>();
+ TransformMeta transformMeta = new TransformMeta("lookup", meta);
+ Variables variables = new Variables();
+
+ meta.check(
+ remarks,
+ mock(PipelineMeta.class),
+ transformMeta,
+ new RowMeta(),
+ new String[] {"prev"},
+ new String[] {},
+ null,
+ variables,
+ metadataProvider);
+
+ assertTrue(
+ remarks.stream().anyMatch(r -> r.getType() ==
ICheckResult.TYPE_RESULT_ERROR),
+ "Expected a missing-connection error remark");
+ }
+
+ @Test
+ void check_ReportsErrorWhenNoInputReceived() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("postgres");
+ meta.getLookup().setTableName("");
+ List<ICheckResult> remarks = new ArrayList<>();
+ TransformMeta transformMeta = new TransformMeta("lookup", meta);
+ Variables variables = new Variables();
+
+ // Connection exists in metadata but connecting to NONE DB may fail —
still should report
+ // no-input when input array is empty.
+ meta.check(
+ remarks,
+ mock(PipelineMeta.class),
+ transformMeta,
+ new RowMeta(),
+ new String[] {},
+ new String[] {},
+ null,
+ variables,
+ metadataProvider);
+
+ assertTrue(
+ remarks.stream()
+ .anyMatch(
+ r ->
+ r.getType() == ICheckResult.TYPE_RESULT_ERROR
+ && r.getText() != null
+ && r.getText().toLowerCase().contains("no input")),
+ "Expected a no-input error remark, got: " + remarks);
+ }
+
+ @Test
+ void check_ReportsOkWhenReceivingInput() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("postgres");
+ meta.getLookup().setTableName("");
+ List<ICheckResult> remarks = new ArrayList<>();
+ TransformMeta transformMeta = new TransformMeta("lookup", meta);
+ Variables variables = new Variables();
+
+ meta.check(
+ remarks,
+ mock(PipelineMeta.class),
+ transformMeta,
+ new RowMeta(),
+ new String[] {"prev"},
+ new String[] {},
+ null,
+ variables,
+ metadataProvider);
+
+ assertTrue(
+ remarks.stream()
+ .anyMatch(
+ r ->
+ r.getType() == ICheckResult.TYPE_RESULT_OK
+ && r.getText() != null
+ && r.getText().toLowerCase().contains("receiving")),
+ "Expected an OK remark about receiving input, got: " + remarks);
+ }
+
+ @Test
+ void analyseImpact_AddsReadImpactForKeysAndReturns() throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("postgres");
+ Lookup lookup = meta.getLookup();
+ lookup.setTableName("users");
+ lookup.getKeyFields().add(new KeyField("in_id", "", "=", "id"));
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "name",
+ "user_name",
+ "",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ IRowMeta prev = new RowMeta();
+ prev.addValueMeta(new ValueMetaString("in_id"));
+
+ List<DatabaseImpact> impact = new ArrayList<>();
+ TransformMeta transformMeta = new TransformMeta("lookup", meta);
+ PipelineMeta pipelineMeta = mock(PipelineMeta.class);
+ when(pipelineMeta.getName()).thenReturn("pipe");
+
+ meta.analyseImpact(
+ new Variables(),
+ impact,
+ pipelineMeta,
+ transformMeta,
+ prev,
+ new String[] {},
+ new String[] {},
+ null,
+ metadataProvider);
+
+ assertEquals(2, impact.size());
+ assertEquals(DatabaseImpact.TYPE_IMPACT_READ, impact.get(0).getType());
+ assertEquals("id", impact.get(0).getField());
+ assertEquals(DatabaseImpact.TYPE_IMPACT_READ, impact.get(1).getType());
+ assertEquals("name", impact.get(1).getField());
+ }
}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupUTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupUTest.java
index be879a2073..13bf628aa9 100644
---
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupUTest.java
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DatabaseLookupUTest.java
@@ -21,7 +21,11 @@ import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -35,12 +39,15 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockingDetails;
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.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@@ -358,9 +365,7 @@ class DatabaseLookupUTest {
DatabaseLookupData data)
throws HopException {
- NoneDatabaseMeta genericMeta = new NoneDatabaseMeta();
- DatabaseMeta dbMeta = new DatabaseMeta();
- dbMeta.setIDatabase(genericMeta);
+ DatabaseMeta dbMeta = createNoneDbMeta();
DatabaseLookup transform = spyLookup(mockHelper, meta, data, db, dbMeta);
when(transform.getPipelineMeta().findDatabase(any(String.class),
any(IVariables.class)))
@@ -406,6 +411,292 @@ class DatabaseLookupUTest {
assertNotNull(data.cache.getRowFromCache(data.lookupMeta, new Object[]
{2L}));
}
+ @Test
+ void infersReturnTypeFromTableWhenDefaultTypeEmpty() throws Exception {
+ Database db = mock(Database.class);
+
+ RowMeta tableFields = new RowMeta();
+ tableFields.addValueMeta(new ValueMetaInteger("ID"));
+ tableFields.addValueMeta(new ValueMetaString("stock_name"));
+ when(db.getTableFields(nullable(String.class))).thenReturn(tableFields);
+
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("connection1");
+ Lookup lookup = meta.getLookup();
+ lookup.setTableName("VirtualTable");
+ lookup.getKeyFields().add(new KeyField("id", "", "=", "ID"));
+ lookup
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "stock_name",
+ "",
+ "",
+ "",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ DatabaseMeta dbMeta = createNoneDbMeta();
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db, dbMeta);
+ when(transform.getPipelineMeta().findDatabase(any(String.class),
any(IVariables.class)))
+ .thenReturn(dbMeta);
+ doReturn(null).when(transform).lookupValues(any(IRowMeta.class),
any(Object[].class));
+
+ RowMeta input = new RowMeta();
+ input.addValueMeta(new ValueMetaInteger("id"));
+ transform.setInputRowMeta(input);
+
+ transform.init();
+ data.db = db;
+
+ transform.processRow();
+
+ IValueMeta stockName = data.outputRowMeta.searchValueMeta("stock_name");
+ assertNotNull(stockName);
+ assertThat(stockName.getType(), is(IValueMeta.TYPE_STRING));
+ assertThat(data.returnValueTypes[0], is(IValueMeta.TYPE_STRING));
+ }
+
+ @Test
+ void init_SetsHasDbConditionForLikeOperator() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("connection1");
+ meta.getLookup().getKeyFields().add(new KeyField("id", "", "LIKE", "ID"));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db,
createNoneDbMeta());
+ when(transform.getPipelineMeta().findDatabase(any(String.class),
any(IVariables.class)))
+ .thenReturn(createNoneDbMeta());
+
+ assertTrue(transform.init());
+ assertTrue(data.hasDBCondition);
+ assertFalse(data.allEquals);
+ assertEquals(DatabaseLookupMeta.CONDITION_LIKE, data.conditions[0]);
+ }
+
+ @Test
+ void init_ReturnsFalseWhenConnectionMissing() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("");
+ DatabaseLookupData data = new DatabaseLookupData();
+ DatabaseLookup transform =
+ new DatabaseLookup(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+
+ assertFalse(transform.init());
+ }
+
+ @Test
+ void lookupValues_ReturnsDefaultNullIfWhenNoMatch() throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setCached(false);
+ meta.getLookup().getKeyFields().add(new KeyField("id", "", "=", "ID"));
+ meta.getLookup()
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "name",
+ "",
+ "N/A",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ when(db.getLookup(anyBoolean())).thenReturn(null);
+
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db,
createNoneDbMeta());
+ when(transform.getPipelineMeta().findDatabase(any(String.class),
any(IVariables.class)))
+ .thenReturn(createNoneDbMeta());
+ doReturn(false).when(transform).isRowLevel();
+
+ RowMeta input = new RowMeta();
+ input.addValueMeta(new ValueMetaInteger("id"));
+ transform.setInputRowMeta(input);
+
+ data.db = db;
+ data.keynrs = new int[] {0};
+ data.keynrs2 = new int[] {-1};
+ data.lookupMeta = new RowMeta();
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("ID"));
+ data.returnMeta = new RowMeta();
+ data.returnMeta.addValueMeta(new ValueMetaString("name"));
+ data.outputRowMeta = input.clone();
+ data.outputRowMeta.addValueMeta(new ValueMetaString("name"));
+ data.returnValueTypes = new int[] {IValueMeta.TYPE_STRING};
+ data.nullif = new Object[] {"N/A"};
+ data.trimIndexes = new ArrayList<>();
+
+ Object[] result = transform.lookupValues(input, new Object[] {1L});
+ assertNotNull(result);
+ assertEquals("N/A", result[1]);
+ }
+
+ @Test
+ void lookupValues_EatsRowWhenEatingRowOnLookupFailure() throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setCached(false);
+ meta.getLookup().setEatingRowOnLookupFailure(true);
+ meta.getLookup().getKeyFields().add(new KeyField("id", "", "=", "ID"));
+ meta.getLookup()
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "name",
+ "",
+ "",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ when(db.getLookup(anyBoolean())).thenReturn(null);
+
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db,
createNoneDbMeta());
+ doReturn(false).when(transform).isRowLevel();
+
+ RowMeta input = new RowMeta();
+ input.addValueMeta(new ValueMetaInteger("id"));
+ transform.setInputRowMeta(input);
+
+ data.db = db;
+ data.keynrs = new int[] {0};
+ data.keynrs2 = new int[] {-1};
+ data.lookupMeta = new RowMeta();
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("ID"));
+ data.returnMeta = new RowMeta();
+ data.returnMeta.addValueMeta(new ValueMetaString("name"));
+ data.outputRowMeta = input.clone();
+ data.outputRowMeta.addValueMeta(new ValueMetaString("name"));
+ data.returnValueTypes = new int[] {IValueMeta.TYPE_STRING};
+ data.nullif = new Object[] {null};
+ data.trimIndexes = new ArrayList<>();
+
+ assertNull(transform.lookupValues(input, new Object[] {1L}));
+ }
+
+ @Test
+ void lookupValues_StoresInCacheOnDbHitWhenCached() throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setCached(true);
+ meta.setLoadingAllDataInCache(false);
+ meta.getLookup().getKeyFields().add(new KeyField("id", "", "=", "ID"));
+ meta.getLookup()
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "name",
+ "",
+ "",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ data.allEquals = true;
+ data.cache = mock(DatabaseLookupData.ICache.class);
+ when(data.cache.getRowFromCache(any(IRowMeta.class),
any(Object[].class))).thenReturn(null);
+
+ Database db = mock(Database.class);
+ when(db.getLookup(anyBoolean())).thenReturn(new Object[] {"Alice"});
+ RowMeta returnRowMeta = new RowMeta();
+ returnRowMeta.addValueMeta(new ValueMetaString("name"));
+ when(db.getReturnRowMeta()).thenReturn(returnRowMeta);
+
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db,
createNoneDbMeta());
+ doReturn(false).when(transform).isRowLevel();
+
+ RowMeta input = new RowMeta();
+ input.addValueMeta(new ValueMetaInteger("id"));
+ transform.setInputRowMeta(input);
+
+ data.db = db;
+ data.keynrs = new int[] {0};
+ data.keynrs2 = new int[] {-1};
+ data.lookupMeta = new RowMeta();
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("ID"));
+ data.returnMeta = new RowMeta();
+ data.returnMeta.addValueMeta(new ValueMetaString("name"));
+ data.outputRowMeta = input.clone();
+ data.outputRowMeta.addValueMeta(new ValueMetaString("name"));
+ data.returnValueTypes = new int[] {IValueMeta.TYPE_STRING};
+ data.nullif = new Object[] {null};
+ data.trimIndexes = new ArrayList<>();
+
+ Object[] result = transform.lookupValues(input, new Object[] {1L});
+ assertEquals("Alice", result[1]);
+ verify(data.cache, times(1))
+ .storeRowInCache(any(DatabaseLookupMeta.class), any(IRowMeta.class),
any(), any());
+ }
+
+ @Test
+ void determineFieldsTypesQueryingDb_ThrowsWhenKeyColumnMissingInTable()
throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("connection1");
+ meta.getLookup().setTableName("t");
+ meta.getLookup().getKeyFields().add(new KeyField("id", "", "=",
"MISSING"));
+
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ RowMeta tableFields = new RowMeta();
+ tableFields.addValueMeta(new ValueMetaInteger("ID"));
+ when(db.getTableFields(nullable(String.class))).thenReturn(tableFields);
+
+ DatabaseLookup transform = spyLookup(mockHelper, meta, data, db,
createNoneDbMeta());
+ when(transform.getPipelineMeta().findDatabase(any(String.class),
any(IVariables.class)))
+ .thenReturn(createNoneDbMeta());
+ data.db = db;
+
+ assertThrows(HopException.class,
transform::determineFieldsTypesQueryingDb);
+ }
+
+ @Test
+ void stopRunning_CancelsQueryOnce() throws Exception {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("connection1");
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ data.db = db;
+ data.isCanceled = false;
+
+ DatabaseLookup transform =
+ new DatabaseLookup(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+
+ transform.stopRunning();
+ transform.stopRunning();
+
+ verify(db, times(1)).cancelQuery();
+ assertTrue(data.isCanceled);
+ }
+
+ @Test
+ void dispose_ClearsCacheAndDisconnects() {
+ DatabaseLookupMeta meta = new DatabaseLookupMeta();
+ meta.setConnection("connection1");
+ DatabaseLookupData data = new DatabaseLookupData();
+ Database db = mock(Database.class);
+ data.db = db;
+ data.cache = DefaultCache.newCache(data, 16);
+
+ DatabaseLookup transform =
+ new DatabaseLookup(
+ mockHelper.transformMeta, meta, data, 0, mockHelper.pipelineMeta,
mockHelper.pipeline);
+
+ transform.dispose();
+
+ verify(db, times(1)).disconnect();
+ assertNull(data.db);
+ assertNull(data.cache);
+ }
+
+ private DatabaseMeta createNoneDbMeta() {
+ NoneDatabaseMeta genericMeta = new NoneDatabaseMeta();
+ DatabaseMeta dbMeta = new DatabaseMeta();
+ dbMeta.setIDatabase(genericMeta);
+ return dbMeta;
+ }
+
public class MockDatabaseLookup extends DatabaseLookup {
public MockDatabaseLookup(
TransformMeta transformMeta,
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DefaultCacheTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DefaultCacheTest.java
new file mode 100644
index 0000000000..84a63c210a
--- /dev/null
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/DefaultCacheTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.databaselookup;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.row.IRowMeta;
+import org.apache.hop.core.row.RowMeta;
+import org.apache.hop.core.row.value.ValueMetaInteger;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class DefaultCacheTest {
+
+ private DatabaseLookupData data;
+ private IRowMeta lookupMeta;
+ private DatabaseLookupMeta meta;
+
+ @BeforeEach
+ void setUp() {
+ data = new DatabaseLookupData();
+ data.lookupMeta = new RowMeta();
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("id"));
+ lookupMeta = data.lookupMeta.clone();
+
+ meta = new DatabaseLookupMeta();
+ meta.setCached(true);
+ meta.setCacheSize(0);
+ meta.setLoadingAllDataInCache(false);
+ }
+
+ @Test
+ void newCache_UsesDefaultCapacityWhenSizeNonPositive() {
+ DefaultCache cache = DefaultCache.newCache(data, 0);
+ assertNotNull(cache);
+ DefaultCache negative = DefaultCache.newCache(data, -5);
+ assertNotNull(negative);
+ }
+
+ @Test
+ void newCache_AcceptsPositiveCacheSize() {
+ assertNotNull(DefaultCache.newCache(data, 10));
+ }
+
+ @Test
+ void getRowFromCache_AllEquals_ReturnsExactMapHit() throws Exception {
+ data.allEquals = true;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_EQ};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ Object[] key = new Object[] {1L};
+ Object[] value = new Object[] {"one"};
+ cache.storeRowInCache(meta, lookupMeta, key, value);
+
+ assertArrayEquals(value, cache.getRowFromCache(lookupMeta, new Object[]
{1L}));
+ assertNull(cache.getRowFromCache(lookupMeta, new Object[] {2L}));
+ }
+
+ @Test
+ void getRowFromCache_ConditionEq_ScansWhenNotAllEquals() throws Exception {
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_EQ, 5L, 5L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_EQ, 5L, 6L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionNe() throws Exception {
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_NE, 5L, 6L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_NE, 5L, 5L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionLt() throws Exception {
+ // cached key < lookup → match when lookup > key
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_LT, 5L, 6L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_LT, 5L, 4L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionLe() throws Exception {
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_LE, 5L, 5L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_LE, 5L, 6L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_LE, 5L, 4L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionGt() throws Exception {
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_GT, 5L, 4L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_GT, 5L, 5L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionGe() throws Exception {
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_GE, 5L, 5L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_GE, 5L, 4L, true);
+ assertConditionMatch(DatabaseLookupMeta.CONDITION_GE, 5L, 6L, false);
+ }
+
+ @Test
+ void getRowFromCache_ConditionIsNull() throws Exception {
+ data.allEquals = false;
+ data.hasDBCondition = false;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_IS_NULL};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ Object[] nullKey = new Object[] {null};
+ Object[] value = new Object[] {"null-row"};
+ cache.storeRowInCache(meta, lookupMeta, nullKey, value);
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {1L}, new Object[]
{"one"});
+
+ assertArrayEquals(value, cache.getRowFromCache(lookupMeta, new Object[]
{999L}));
+ }
+
+ @Test
+ void getRowFromCache_ConditionIsNotNull() throws Exception {
+ data.allEquals = false;
+ data.hasDBCondition = false;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_IS_NOT_NULL};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {null}, new Object[]
{"null-row"});
+ Object[] value = new Object[] {"one"};
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {1L}, value);
+
+ assertArrayEquals(value, cache.getRowFromCache(lookupMeta, new Object[]
{999L}));
+ }
+
+ @Test
+ void getRowFromCache_ConditionBetween() throws Exception {
+ data.allEquals = false;
+ data.hasDBCondition = false;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_BETWEEN};
+ data.lookupMeta = new RowMeta();
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("from"));
+ data.lookupMeta.addValueMeta(new ValueMetaInteger("to"));
+ lookupMeta = data.lookupMeta.clone();
+
+ // BETWEEN stores a single key column in the map key
+ IRowMeta storeMeta = new RowMeta();
+ storeMeta.addValueMeta(new ValueMetaInteger("id"));
+
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+ Object[] value = new Object[] {"mid"};
+ cache.storeRowInCache(meta, storeMeta, new Object[] {5L}, value);
+
+ IRowMeta lookupWithRange = new RowMeta();
+ lookupWithRange.addValueMeta(new ValueMetaInteger("from"));
+ lookupWithRange.addValueMeta(new ValueMetaInteger("to"));
+
+ assertArrayEquals(value, cache.getRowFromCache(lookupWithRange, new
Object[] {1L, 10L}));
+ assertNull(cache.getRowFromCache(lookupWithRange, new Object[] {6L, 10L}));
+ }
+
+ @Test
+ void getRowFromCache_UnknownCondition_SetsHasDbConditionAndStopsMatching()
throws Exception {
+ data.allEquals = false;
+ data.hasDBCondition = false;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_LIKE};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {1L}, new Object[]
{"one"});
+
+ assertNull(cache.getRowFromCache(lookupMeta, new Object[] {1L}));
+ assertTrue(data.hasDBCondition);
+ }
+
+ @Test
+ void getRowFromCache_HasDbCondition_SkipsScanAndReturnsNull() throws
Exception {
+ data.allEquals = false;
+ data.hasDBCondition = true;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_LT};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {1L}, new Object[]
{"one"});
+
+ assertNull(cache.getRowFromCache(lookupMeta, new Object[] {10L}));
+ }
+
+ @Test
+ void storeRowInCache_DoesNotEvictWhenLoadingAllDataInCache() throws
Exception {
+ meta.setLoadingAllDataInCache(true);
+ meta.setCacheSize(2);
+ data.allEquals = true;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_EQ};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ for (long i = 0; i < 5; i++) {
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {i}, new Object[]
{"v" + i});
+ }
+
+ // All entries remain when load-all is enabled
+ for (long i = 0; i < 5; i++) {
+ assertNotNull(cache.getRowFromCache(lookupMeta, new Object[] {i}));
+ }
+ }
+
+ @Test
+ void storeRowInCache_EvictsWhenOverCacheSize() throws Exception {
+ meta.setLoadingAllDataInCache(false);
+ meta.setCacheSize(5);
+ data.allEquals = true;
+ data.conditions = new int[] {DatabaseLookupMeta.CONDITION_EQ};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ for (long i = 0; i < 20; i++) {
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {i}, new Object[]
{"v" + i});
+ // small delay so TimedRow timestamps differ enough for eviction sampling
+ Thread.sleep(2);
+ }
+
+ int hits = 0;
+ for (long i = 0; i < 20; i++) {
+ if (cache.getRowFromCache(lookupMeta, new Object[] {i}) != null) {
+ hits++;
+ }
+ }
+ // Eviction is approximate (samples ~10%); still fewer than all 20 should
remain
+ assertTrue(hits < 20, "Expected eviction to remove some entries, hits=" +
hits);
+ assertTrue(hits > 0, "Expected some entries to remain");
+ }
+
+ private void assertConditionMatch(int condition, long cachedKey, long
lookupValue, boolean expect)
+ throws Exception {
+ data.allEquals = false;
+ data.hasDBCondition = false;
+ data.conditions = new int[] {condition};
+ DefaultCache cache = DefaultCache.newCache(data, 16);
+
+ Object[] value = new Object[] {"hit"};
+ cache.storeRowInCache(meta, lookupMeta, new Object[] {cachedKey}, value);
+
+ Object[] found = cache.getRowFromCache(lookupMeta, new Object[]
{lookupValue});
+ if (expect) {
+ assertArrayEquals(value, found);
+ } else {
+ assertNull(found);
+ }
+ assertFalse(data.hasDBCondition);
+ }
+}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/KeyFieldTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/KeyFieldTest.java
new file mode 100644
index 0000000000..58f5c4b5a9
--- /dev/null
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/KeyFieldTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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.databaselookup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+class KeyFieldTest {
+
+ @Test
+ void defaultConstructor_LeavesFieldsNull() {
+ KeyField key = new KeyField();
+ assertNull(key.getStreamField1());
+ assertNull(key.getStreamField2());
+ assertNull(key.getCondition());
+ assertNull(key.getTableField());
+ }
+
+ @Test
+ void allArgsConstructor_SetsFields() {
+ KeyField key = new KeyField("stream1", "stream2", "=", "table_col");
+ assertEquals("stream1", key.getStreamField1());
+ assertEquals("stream2", key.getStreamField2());
+ assertEquals("=", key.getCondition());
+ assertEquals("table_col", key.getTableField());
+ }
+
+ @Test
+ void copyConstructor_CopiesAllFieldsIndependently() {
+ KeyField original = new KeyField("stream1", "stream2", "=", "table_col");
+ KeyField copy = new KeyField(original);
+
+ assertNotSame(original, copy);
+ assertEquals(original.getStreamField1(), copy.getStreamField1());
+ assertEquals(original.getStreamField2(), copy.getStreamField2());
+ assertEquals(original.getCondition(), copy.getCondition());
+ assertEquals(original.getTableField(), copy.getTableField());
+
+ copy.setStreamField1("changed");
+ copy.setStreamField2("changed2");
+ copy.setCondition("<>");
+ copy.setTableField("other");
+ assertEquals("stream1", original.getStreamField1());
+ assertEquals("stream2", original.getStreamField2());
+ assertEquals("=", original.getCondition());
+ assertEquals("table_col", original.getTableField());
+ }
+
+ @Test
+ void settersAndGetters() {
+ KeyField key = new KeyField();
+ key.setStreamField1("a");
+ key.setStreamField2("b");
+ key.setCondition("LIKE");
+ key.setTableField("c");
+
+ assertEquals("a", key.getStreamField1());
+ assertEquals("b", key.getStreamField2());
+ assertEquals("LIKE", key.getCondition());
+ assertEquals("c", key.getTableField());
+ }
+}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/LookupTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/LookupTest.java
new file mode 100644
index 0000000000..cea13baf16
--- /dev/null
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/LookupTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.databaselookup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.junit.jupiter.api.Test;
+
+class LookupTest {
+
+ @Test
+ void defaultConstructor_StartsEmpty() {
+ Lookup lookup = new Lookup();
+ assertTrue(lookup.getKeyFields().isEmpty());
+ assertTrue(lookup.getReturnValues().isEmpty());
+ assertFalse(lookup.isFailingOnMultipleResults());
+ assertFalse(lookup.isEatingRowOnLookupFailure());
+ }
+
+ @Test
+ void settersAndGetters() {
+ Lookup lookup = new Lookup();
+ lookup.setSchemaName("public");
+ lookup.setTableName("users");
+ lookup.setOrderByClause("id DESC");
+ lookup.setFailingOnMultipleResults(true);
+ lookup.setEatingRowOnLookupFailure(true);
+
+ assertEquals("public", lookup.getSchemaName());
+ assertEquals("users", lookup.getTableName());
+ assertEquals("id DESC", lookup.getOrderByClause());
+ assertTrue(lookup.isFailingOnMultipleResults());
+ assertTrue(lookup.isEatingRowOnLookupFailure());
+ }
+
+ @Test
+ void clone_DeepCopiesKeysAndReturns() {
+ Lookup original = new Lookup();
+ original.setSchemaName("public");
+ original.setTableName("users");
+ original.setOrderByClause("id DESC");
+ original.setFailingOnMultipleResults(true);
+ original.setEatingRowOnLookupFailure(true);
+ original.getKeyFields().add(new KeyField("in_id", "", "=", "id"));
+ original
+ .getReturnValues()
+ .add(
+ new ReturnValue(
+ "name",
+ "user_name",
+ "",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_NONE)));
+
+ Lookup clone = original.clone();
+
+ assertNotSame(original, clone);
+ assertNotSame(original.getKeyFields(), clone.getKeyFields());
+ assertNotSame(original.getReturnValues(), clone.getReturnValues());
+ assertEquals("public", clone.getSchemaName());
+ assertEquals("users", clone.getTableName());
+ assertEquals("id DESC", clone.getOrderByClause());
+ assertTrue(clone.isFailingOnMultipleResults());
+ assertTrue(clone.isEatingRowOnLookupFailure());
+ assertEquals(1, clone.getKeyFields().size());
+ assertEquals("in_id", clone.getKeyFields().get(0).getStreamField1());
+ assertEquals(1, clone.getReturnValues().size());
+ assertEquals("user_name", clone.getReturnValues().get(0).getNewName());
+
+ clone.getKeyFields().get(0).setStreamField1("changed");
+ clone.getReturnValues().get(0).setNewName("changed");
+ assertEquals("in_id", original.getKeyFields().get(0).getStreamField1());
+ assertEquals("user_name", original.getReturnValues().get(0).getNewName());
+ }
+}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/ReturnValueTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/ReturnValueTest.java
new file mode 100644
index 0000000000..de7c724c74
--- /dev/null
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/ReturnValueTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.databaselookup;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.apache.hop.core.row.IValueMeta;
+import org.apache.hop.core.row.value.ValueMetaString;
+import org.junit.jupiter.api.Test;
+
+class ReturnValueTest {
+
+ @Test
+ void defaultConstructor_LeavesFieldsNull() {
+ ReturnValue value = new ReturnValue();
+ assertNull(value.getTableField());
+ assertNull(value.getNewName());
+ assertNull(value.getDefaultValue());
+ assertNull(value.getDefaultType());
+ assertNull(value.getTrimType());
+ }
+
+ @Test
+ void allArgsConstructor_SetsFields() {
+ String trim = ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_BOTH);
+ ReturnValue value = new ReturnValue("col", "rename", "?", "String", trim);
+
+ assertEquals("col", value.getTableField());
+ assertEquals("rename", value.getNewName());
+ assertEquals("?", value.getDefaultValue());
+ assertEquals("String", value.getDefaultType());
+ assertEquals(trim, value.getTrimType());
+ }
+
+ @Test
+ void copyConstructor_CopiesAllFieldsIndependently() {
+ ReturnValue original =
+ new ReturnValue(
+ "col",
+ "rename",
+ "?",
+ "String",
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_BOTH));
+ ReturnValue copy = new ReturnValue(original);
+
+ assertNotSame(original, copy);
+ assertEquals(original.getTableField(), copy.getTableField());
+ assertEquals(original.getNewName(), copy.getNewName());
+ assertEquals(original.getDefaultValue(), copy.getDefaultValue());
+ assertEquals(original.getDefaultType(), copy.getDefaultType());
+ assertEquals(original.getTrimType(), copy.getTrimType());
+
+ copy.setTableField("t2");
+ copy.setNewName("other");
+ copy.setDefaultValue("x");
+ copy.setDefaultType("Integer");
+
copy.setTrimType(ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_LEFT));
+ assertEquals("col", original.getTableField());
+ assertEquals("rename", original.getNewName());
+ assertEquals("?", original.getDefaultValue());
+ assertEquals("String", original.getDefaultType());
+ assertEquals(
+ ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_BOTH),
original.getTrimType());
+ }
+
+ @Test
+ void settersAndGetters() {
+ ReturnValue value = new ReturnValue();
+ value.setTableField("t");
+ value.setNewName("n");
+ value.setDefaultValue("d");
+ value.setDefaultType("Integer");
+
value.setTrimType(ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_LEFT));
+
+ assertEquals("t", value.getTableField());
+ assertEquals("n", value.getNewName());
+ assertEquals("d", value.getDefaultValue());
+ assertEquals("Integer", value.getDefaultType());
+ assertEquals(ValueMetaString.getTrimTypeCode(IValueMeta.TRIM_TYPE_LEFT),
value.getTrimType());
+ }
+}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/IndexTestBase.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/IndexTestBase.java
index ab77dcc895..52ab5e16e1 100644
---
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/IndexTestBase.java
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/IndexTestBase.java
@@ -24,25 +24,16 @@ import static org.junit.jupiter.api.Assertions.fail;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
+import java.util.stream.Stream;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.value.ValueMetaInteger;
import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
/**
- * This is a base class for several similar cases. All of them are checking
how indexes work with
- * the same tuple of data: [0, 1, 2, 2, 3]. Since the data set is known, each
subclass show
- * implement tests for the following values:
- *
- * <ul>
- * <li>-1
- * <li>0
- * <li>1
- * <li>2
- * <li>3
- * <li>100
- * </ul>
+ * Base class for index cases against the shared data set family [0, 1, 2, 2,
3] (sorted, reverse,
+ * shuffled). Subclasses implement assertions for lookups of -1, 0, 1, 2, 3,
and 100.
*/
public abstract class IndexTestBase<T extends Index> {
@@ -54,32 +45,43 @@ public abstract class IndexTestBase<T extends Index> {
return result;
}
+ static Stream<Long[][]> sampleData() {
+ return Stream.of(toMatrix(0, 1, 2, 2, 3), toMatrix(3, 2, 2, 1, 0),
toMatrix(1, 3, 2, 0, 2));
+ }
+
+ /** Retained for subclasses that still reference the old helper. */
static List<Object[]> createSampleData() {
- // sorted, reversely sorted, and shuffled data
return Arrays.asList(
new Object[] {toMatrix(0, 1, 2, 2, 3)},
new Object[] {toMatrix(3, 2, 2, 1, 0)},
new Object[] {toMatrix(1, 3, 2, 0, 2)});
}
- final Long[][] rows;
private final Class<T> clazz;
+ Long[][] rows;
T index;
SearchingContext context;
- public IndexTestBase(Class<T> clazz, Long[][] rows) {
- this.rows = rows;
+ public IndexTestBase(Class<T> clazz) {
this.clazz = clazz;
}
- @BeforeEach
- void setUp() throws Exception {
- index = createIndexInstance(0, new ValueMetaInteger(), 5);
+ /**
+ * @deprecated use {@link #IndexTestBase(Class)}
+ */
+ @Deprecated
+ public IndexTestBase(Class<T> clazz, Long[][] ignoredRows) {
+ this(clazz);
+ }
+
+ private void prepare(Long[][] sampleRows) throws Exception {
+ this.rows = sampleRows;
+ index = createIndexInstance(0, new ValueMetaInteger(), sampleRows.length);
index.performIndexingOf(rows);
context = new SearchingContext();
- context.init(5);
+ context.init(sampleRows.length);
}
T createIndexInstance(int column, IValueMeta meta, int rowsAmount) throws
Exception {
@@ -92,6 +94,7 @@ public abstract class IndexTestBase<T extends Index> {
void tearDown() {
index = null;
context = null;
+ rows = null;
}
void testFindsNothing(long value) {
@@ -124,21 +127,63 @@ public abstract class IndexTestBase<T extends Index> {
abstract void doAssertMatches(BitSet candidates, long lookupValue, long
actualValue);
- @Test
abstract void lookupFor_MinusOne();
- @Test
abstract void lookupFor_Zero();
- @Test
abstract void lookupFor_One();
- @Test
abstract void lookupFor_Two();
- @Test
abstract void lookupFor_Three();
- @Test
abstract void lookupFor_Hundred();
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_MinusOne(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_MinusOne();
+ }
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_Zero(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_Zero();
+ }
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_One(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_One();
+ }
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_Two(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_Two();
+ }
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_Three(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_Three();
+ }
+
+ @ParameterizedTest
+ @MethodSource(
+
"org.apache.hop.pipeline.transforms.databaselookup.readallcache.IndexTestBase#sampleData")
+ void runsLookupFor_Hundred(Long[][] sampleRows) throws Exception {
+ prepare(sampleRows);
+ lookupFor_Hundred();
+ }
}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/ReadAllCacheTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/ReadAllCacheTest.java
index bfb7917a16..5844cb1651 100644
---
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/ReadAllCacheTest.java
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/ReadAllCacheTest.java
@@ -199,4 +199,13 @@ class ReadAllCacheTest {
Object[] found = cache.getRowFromCache(new RowMeta(), new Object[0]);
assertArrayEquals(new Object[] {"one"}, found, "(keys[1] == 1L) --> row
2");
}
+
+ @Test
+ void builder_BuildsEmptyCacheWhenNoRowsAdded() throws Exception {
+ transformData.conditions = new int[] {DatabaseLookupMeta.CONDITION_EQ};
+ ReadAllCache.Builder builder = new ReadAllCache.Builder(transformData, 0);
+ builder.setKeysMeta(keysMeta);
+ ReadAllCache cache = builder.build();
+ assertNull(cache.getRowFromCache(keysMeta.clone(), keys[0]));
+ }
}
diff --git
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/SearchingContextTest.java
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/SearchingContextTest.java
index fc8468765c..1634afe92f 100644
---
a/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/SearchingContextTest.java
+++
b/plugins/transforms/databaselookup/src/test/java/org/apache/hop/pipeline/transforms/databaselookup/readallcache/SearchingContextTest.java
@@ -50,4 +50,32 @@ class SearchingContextTest {
ctx.intersect(set, false);
assertTrue(ctx.isEmpty(), "Expected to be empty");
}
+
+ @Test
+ void intersect_InverseModeRemovesCandidates() {
+ SearchingContext ctx = new SearchingContext();
+ ctx.init(4);
+
+ BitSet remove = ctx.getWorkingSet();
+ remove.set(0);
+ remove.set(2);
+ ctx.intersect(remove, true);
+
+ assertFalse(ctx.isEmpty());
+ BitSet candidates = ctx.getCandidates();
+ assertFalse(candidates.get(0));
+ assertTrue(candidates.get(1));
+ assertFalse(candidates.get(2));
+ assertTrue(candidates.get(3));
+ }
+
+ @Test
+ void setEmpty_MarksContextEmpty() {
+ SearchingContext ctx = new SearchingContext();
+ ctx.init(3);
+ assertFalse(ctx.isEmpty());
+
+ ctx.setEmpty();
+ assertTrue(ctx.isEmpty());
+ }
}