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 44d18ff53c fix eager metadata loading, fixes #8203 (#8210)
44d18ff53c is described below
commit 44d18ff53cfbb77f2b74e84dff954c4ef38d5cce
Author: Hans Van Akelyen <[email protected]>
AuthorDate: Tue Sep 1 16:46:51 2026 +0200
fix eager metadata loading, fixes #8203 (#8210)
---
.../xp/LocationMouseDoubleClickExtensionPoint.java | 198 ++++++++++-----------
.../testing/PipelineUnitTestSetLocationDialog.java | 39 +++-
.../PipelineUnitTestSetLocationDialogTest.java | 175 ++++++++++++++++++
.../transforms/tableinput/TableInputMeta.java | 9 +
.../tableinput/messages/messages_en_US.properties | 1 +
.../transforms/tableinput/TableInputMetaTest.java | 24 +++
6 files changed, 332 insertions(+), 114 deletions(-)
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/LocationMouseDoubleClickExtensionPoint.java
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/LocationMouseDoubleClickExtensionPoint.java
index b83a657208..da459d849f 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/LocationMouseDoubleClickExtensionPoint.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/testing/xp/LocationMouseDoubleClickExtensionPoint.java
@@ -17,9 +17,8 @@
package org.apache.hop.testing.xp;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
+import java.util.function.Function;
import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.extension.ExtensionPoint;
import org.apache.hop.core.extension.IExtensionPoint;
@@ -31,7 +30,6 @@ import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.engine.IPipelineEngine;
-import org.apache.hop.pipeline.transform.TransformMeta;
import org.apache.hop.testing.DataSet;
import org.apache.hop.testing.PipelineUnitTest;
import org.apache.hop.testing.PipelineUnitTestSetLocation;
@@ -39,7 +37,6 @@ import org.apache.hop.testing.UnitTestResult;
import org.apache.hop.testing.gui.TestingGuiPlugin;
import org.apache.hop.testing.util.DataSetConst;
import org.apache.hop.ui.core.dialog.ErrorDialog;
-import org.apache.hop.ui.core.metadata.MetadataManager;
import org.apache.hop.ui.hopgui.HopGui;
import org.apache.hop.ui.hopgui.file.pipeline.HopGuiPipelineGraph;
import
org.apache.hop.ui.hopgui.file.pipeline.extension.HopGuiPipelineGraphExtension;
@@ -65,119 +62,104 @@ public class LocationMouseDoubleClickExtensionPoint
return;
}
+ // This is called for every mouse down on the canvas so figure out first
whether one of the
+ // unit test markers was clicked on. Only then do any real work: loading
the data sets and
+ // determining transform fields is far too expensive to do on every click
(issue #8203).
+ //
+ MouseEvent e = pipelineGraphExtension.getEvent();
+ if (e.button != 1 && e.button != 2) {
+ return;
+ }
+ Point point = pipelineGraphExtension.getPoint();
+ AreaOwner areaOwner = pipelineGraph.getVisibleAreaOwner(point.x, point.y);
+ if (areaOwner == null || areaOwner.getAreaType() == null) {
+ return;
+ }
+ Object area = areaOwner.getParent();
+ boolean inputDataSet = DataSetConst.AREA_DRAWN_INPUT_DATA_SET.equals(area);
+ boolean goldenDataSet =
DataSetConst.AREA_DRAWN_GOLDEN_DATA_SET.equals(area);
+ boolean goldenDataResult =
DataSetConst.AREA_DRAWN_GOLDEN_DATA_RESULT.equals(area);
+ if (!inputDataSet && !goldenDataSet && !goldenDataResult) {
+ return;
+ }
+
HopGui hopGui = HopGui.getInstance();
try {
- List<DataSet> dataSets =
hopGui.getMetadataProvider().getSerializer(DataSet.class).loadAll();
+ String transformName = (String) areaOwner.getOwner();
+
+ if (goldenDataResult) {
+ pipelineGraphExtension.setPreventingDefault(true);
+ showGoldenDataResult(pipelineGraph, hopGui, unitTest, transformName);
+ return;
+ }
- Map<String, IRowMeta> transformFieldsMap = new HashMap<>();
- for (TransformMeta transformMeta : pipelineMeta.getTransforms()) {
- try {
- IRowMeta transformFields =
- pipelineMeta.getTransformFields(pipelineGraph.getVariables(),
transformMeta);
- transformFieldsMap.put(transformMeta.getName(), transformFields);
- } catch (Exception e) {
- // Ignore GUI errors...
- }
+ pipelineGraphExtension.setPreventingDefault(true);
+
+ PipelineUnitTestSetLocation location =
+ inputDataSet
+ ? unitTest.findInputLocation(transformName)
+ : unitTest.findGoldenLocation(transformName);
+ if (location == null) {
+ return;
}
- // Find the location that was double-clicked on...
- //
- MouseEvent e = pipelineGraphExtension.getEvent();
- Point point = pipelineGraphExtension.getPoint();
-
- if (e.button == 1 || e.button == 2) {
- AreaOwner areaOwner = pipelineGraph.getVisibleAreaOwner(point.x,
point.y);
- if (areaOwner != null && areaOwner.getAreaType() != null) {
- // Check if this is the flask...
- //
- if
(DataSetConst.AREA_DRAWN_INPUT_DATA_SET.equals(areaOwner.getParent())) {
- pipelineGraphExtension.setPreventingDefault(true);
-
- // Open the dataset double-clicked on...
- //
- String transformName = (String) areaOwner.getOwner();
-
- PipelineUnitTestSetLocation inputLocation =
unitTest.findInputLocation(transformName);
- if (inputLocation != null) {
- pipelineGraphExtension.setPreventingDefault(true);
- PipelineUnitTestSetLocationDialog dialog =
- new PipelineUnitTestSetLocationDialog(
- hopGui.getActiveShell(),
- variables,
- hopGui.getMetadataProvider(),
- inputLocation,
- dataSets,
- transformFieldsMap);
- if (dialog.open()) {
-
hopGui.getMetadataProvider().getSerializer(PipelineUnitTest.class).save(unitTest);
- pipelineGraph.updateGui();
- }
- }
- } else if
(DataSetConst.AREA_DRAWN_GOLDEN_DATA_SET.equals(areaOwner.getParent())) {
- pipelineGraphExtension.setPreventingDefault(true);
-
- // Open the dataset double-clicked on...
- //
- String transformName = (String) areaOwner.getOwner();
-
- PipelineUnitTestSetLocation goldenLocation =
unitTest.findGoldenLocation(transformName);
- if (goldenLocation != null) {
- pipelineGraphExtension.setPreventingDefault(true);
- PipelineUnitTestSetLocationDialog dialog =
- new PipelineUnitTestSetLocationDialog(
- hopGui.getActiveShell(),
- variables,
- hopGui.getMetadataProvider(),
- goldenLocation,
- dataSets,
- transformFieldsMap);
- if (dialog.open()) {
- // Save the unit test
-
hopGui.getMetadataProvider().getSerializer(PipelineUnitTest.class).save(unitTest);
- pipelineGraph.updateGui();
- }
- }
- } else if
(DataSetConst.AREA_DRAWN_GOLDEN_DATA_RESULT.equals(areaOwner.getParent())) {
- pipelineGraphExtension.setPreventingDefault(true);
-
- // Open the dataset double-clicked on...
- //
- String transformName = (String) areaOwner.getOwner();
-
- PipelineUnitTestSetLocation goldenLocation =
unitTest.findGoldenLocation(transformName);
- if (goldenLocation != null) {
-
- // Find the errors list of the unit test...
- //
- IPipelineEngine<PipelineMeta> pipeline =
pipelineGraph.getPipeline();
- if (pipeline == null) {
- return;
- }
-
- List<UnitTestResult> results =
- (List<UnitTestResult>)
-
pipeline.getExtensionDataMap().get(DataSetConst.UNIT_TEST_RESULTS);
- if (Utils.isEmpty(results)) {
- return;
- }
-
- pipelineGraphExtension.setPreventingDefault(true);
-
ValidatePipelineUnitTestExtensionPoint.showUnitTestErrors(pipeline, results,
hopGui);
- }
- }
- }
+ List<DataSet> dataSets =
hopGui.getMetadataProvider().getSerializer(DataSet.class).loadAll();
+
+ PipelineUnitTestSetLocationDialog dialog =
+ new PipelineUnitTestSetLocationDialog(
+ hopGui.getActiveShell(),
+ variables,
+ hopGui.getMetadataProvider(),
+ location,
+ dataSets,
+ pipelineMeta.getTransformNames(),
+ transformFieldsResolver(pipelineGraph, pipelineMeta));
+ if (dialog.open()) {
+
hopGui.getMetadataProvider().getSerializer(PipelineUnitTest.class).save(unitTest);
+ pipelineGraph.updateGui();
}
- } catch (Exception e) {
- new ErrorDialog(hopGui.getActiveShell(), "Error", "Error editing
location", e);
+ } catch (Exception e2) {
+ new ErrorDialog(hopGui.getActiveShell(), "Error", "Error editing
location", e2);
}
}
- private void openDataSet(String dataSetName) {
- HopGui hopGui = HopGui.getInstance();
+ /**
+ * Resolve the output fields of a single transform for the dataset location
dialog. The dialog
+ * only asks for the transform it's mapping fields for, which keeps a slow
transform (a Table
+ * input needing a database connection for example) from blocking the whole
dialog.
+ */
+ private Function<String, IRowMeta> transformFieldsResolver(
+ HopGuiPipelineGraph pipelineGraph, PipelineMeta pipelineMeta) {
+ return transformName -> {
+ try {
+ return pipelineMeta.getTransformFields(pipelineGraph.getVariables(),
transformName);
+ } catch (Exception e) {
+ // Ignore GUI errors: the dialog reports unknown fields to the user.
+ //
+ return null;
+ }
+ };
+ }
+
+ private void showGoldenDataResult(
+ HopGuiPipelineGraph pipelineGraph, HopGui hopGui, PipelineUnitTest
unitTest, String name) {
+ if (unitTest.findGoldenLocation(name) == null) {
+ return;
+ }
+
+ // Find the errors list of the unit test...
+ //
+ IPipelineEngine<PipelineMeta> pipeline = pipelineGraph.getPipeline();
+ if (pipeline == null) {
+ return;
+ }
+
+ List<UnitTestResult> results =
+ (List<UnitTestResult>)
pipeline.getExtensionDataMap().get(DataSetConst.UNIT_TEST_RESULTS);
+ if (Utils.isEmpty(results)) {
+ return;
+ }
- MetadataManager<DataSet> manager =
- new MetadataManager<>(
- hopGui.getVariables(), hopGui.getMetadataProvider(),
DataSet.class, hopGui.getShell());
- manager.editMetadata(dataSetName);
+ ValidatePipelineUnitTestExtensionPoint.showUnitTestErrors(pipeline,
results, hopGui);
}
}
diff --git
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialog.java
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialog.java
index dc22e044d9..b9c2c8079e 100644
---
a/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialog.java
+++
b/plugins/misc/testing/src/main/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialog.java
@@ -18,8 +18,10 @@
package org.apache.hop.ui.testing;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import org.apache.hop.core.CheckResult;
import org.apache.hop.core.Const;
@@ -63,7 +65,16 @@ public class PipelineUnitTestSetLocationDialog extends
Dialog {
private final PipelineUnitTestSetLocation location;
private final List<DataSet> dataSets;
- private final Map<String, IRowMeta> transformFieldsMap;
+
+ /**
+ * Resolves the output fields of a transform, or returns null when they
can't be determined.
+ * Called on demand only: resolving the fields of every transform up front
can be very expensive
+ * (issue #8203), for example when a Table input transform has to connect to
a database for it.
+ */
+ private final Function<String, IRowMeta> transformFieldsResolver;
+
+ /** Cache of the fields resolved so far in this dialog, keyed by transform
name. */
+ private final Map<String, IRowMeta> transformFieldsCache = new HashMap<>();
private final String[] transformNames;
private final String[] datasetNames;
@@ -87,17 +98,18 @@ public class PipelineUnitTestSetLocationDialog extends
Dialog {
IHopMetadataProvider metadataProvider,
PipelineUnitTestSetLocation location,
List<DataSet> dataSets,
- Map<String, IRowMeta> transformFieldsMap) {
+ String[] transformNames,
+ Function<String, IRowMeta> transformFieldsResolver) {
super(parent, SWT.NONE);
this.variables = variables;
this.metadataProvider = metadataProvider;
this.location = location;
this.dataSets = dataSets;
- this.transformFieldsMap = transformFieldsMap;
+ this.transformFieldsResolver = transformFieldsResolver;
props = PropsUi.getInstance();
ok = false;
- transformNames = transformFieldsMap.keySet().toArray(new String[0]);
+ this.transformNames = transformNames;
datasetNames = new String[dataSets.size()];
for (int i = 0; i < datasetNames.length; i++) {
datasetNames[i] = dataSets.get(i).getName();
@@ -273,6 +285,21 @@ public class PipelineUnitTestSetLocationDialog extends
Dialog {
return ok;
}
+ /**
+ * Get the output fields of the given transform, resolving them the first
time they are needed.
+ *
+ * @param transformName the transform to get the fields for
+ * @return the fields, or null when they can't be determined
+ */
+ private IRowMeta getTransformFields(String transformName) {
+ // Remember failures as well (null values): retrying is just as expensive
as the first attempt.
+ //
+ if (!transformFieldsCache.containsKey(transformName)) {
+ transformFieldsCache.put(transformName,
transformFieldsResolver.apply(transformName));
+ }
+ return transformFieldsCache.get(transformName);
+ }
+
protected void getFieldMappings() {
try {
@@ -286,7 +313,7 @@ public class PipelineUnitTestSetLocationDialog extends
Dialog {
throw new HopException("Please select a transform and a data set to
map fields between");
}
- IRowMeta transformRowMeta = transformFieldsMap.get(transformName);
+ IRowMeta transformRowMeta = getTransformFields(transformName);
if (transformRowMeta == null) {
throw new HopException("Unable to find fields for transform " +
transformName);
}
@@ -482,7 +509,7 @@ public class PipelineUnitTestSetLocationDialog extends
Dialog {
// Check fields of the transform if selected
IRowMeta transformRowMeta = null;
if (StringUtils.isNotEmpty(transformName)) {
- transformRowMeta = transformFieldsMap.get(transformName);
+ transformRowMeta = getTransformFields(transformName);
if (transformRowMeta == null) {
remarks.add(
new CheckResult(
diff --git
a/plugins/misc/testing/src/test/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialogTest.java
b/plugins/misc/testing/src/test/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialogTest.java
new file mode 100644
index 0000000000..27dd62da8d
--- /dev/null
+++
b/plugins/misc/testing/src/test/java/org/apache/hop/ui/testing/PipelineUnitTestSetLocationDialogTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.ui.testing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+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.value.ValueMetaString;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.serializer.memory.MemoryMetadataProvider;
+import org.apache.hop.testing.DataSet;
+import org.apache.hop.testing.DataSetField;
+import org.apache.hop.testing.PipelineUnitTestSetLocation;
+import org.apache.hop.ui.core.dialog.EnterMappingDialog;
+import org.eclipse.swtbot.swt.finder.SWTBot;
+import org.eclipse.swtbot.swt.finder.widgets.SWTBotShell;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Guards the design-time cost of the data set location dialog, the dialog
opened from the unit test
+ * markers on the pipeline canvas.
+ *
+ * <p>It used to be handed a map holding the output fields of <em>every</em>
transform in the
+ * pipeline, so opening it had to resolve them all first. A transform that
determines its output
+ * from a variable - a Table input reading its connection from {@code
${connection}} for example -
+ * connects to a database to answer that, on the SWT thread, which froze Hop
GUI (issue #8203). The
+ * dialog now resolves the fields of the one transform it is mapping, and only
when it needs them.
+ */
+@Tag("uitest")
+class PipelineUnitTestSetLocationDialogTest extends SwtBotTestBase {
+
+ private static final String MAPPED_TRANSFORM = "Fonte Sql";
+ private static final String DATA_SET_NAME = "raw rows";
+ private static final String[] TRANSFORM_NAMES = {
+ "Iniciar", "Log Inicial", MAPPED_TRANSFORM, "Salva S3", "Metricas Fluxo",
"Log Final"
+ };
+
+ /** Counts how often the fields of a transform were resolved, keyed by
transform name. */
+ private final Map<String, AtomicInteger> resolutions = new
ConcurrentHashMap<>();
+
+ @Test
+ void openingTheDialogResolvesNoTransformFields() {
+ withDialog(
+ parent -> newDialog(parent).open(),
+ bot -> {
+ SWTBot dialogBot = locationShell(bot).bot();
+ // Nothing was asked for yet: the dialog is up without a single
field lookup.
+ assertTrue(
+ resolutions.isEmpty(), "Opening the dialog resolved fields: " +
resolutions.keySet());
+ dialogBot.button(buttonLabel("System.Button.Cancel")).click();
+ });
+
+ assertTrue(resolutions.isEmpty(), "Fields resolved: " +
resolutions.keySet());
+ }
+
+ @Test
+ void fieldsOfTheMappedTransformAreResolvedOnceOnDemand() {
+ withDialog(
+ parent -> newDialog(parent).open(),
+ bot -> {
+ SWTBotShell locationShell = locationShell(bot);
+
+ // Map fields twice: the second round must be served from the
dialog's own cache.
+ //
+ mapFieldsAndCancel(bot, locationShell);
+ mapFieldsAndCancel(bot, locationShell);
+
+
locationShell.bot().button(buttonLabel("System.Button.Cancel")).click();
+ });
+
+ assertEquals(
+ List.of(MAPPED_TRANSFORM),
+ List.copyOf(resolutions.keySet()),
+ "Only the transform being mapped should have its fields resolved");
+ assertEquals(
+ 1, resolutions.get(MAPPED_TRANSFORM).get(), "Resolved fields should be
reused, not redone");
+ }
+
+ private void mapFieldsAndCancel(SWTBot bot, SWTBotShell locationShell) {
+ locationShell
+ .bot()
+ .button(
+ BaseMessages.getString(
+ PipelineUnitTestSetLocationDialog.class,
+ "PipelineUnitTestSetLocationDialog.MapFields.Button"))
+ .click();
+
+ SWTBotShell mappingShell =
+ bot.shell(BaseMessages.getString(EnterMappingDialog.class,
"EnterMappingDialog.Title"));
+ mappingShell.activate();
+ mappingShell.bot().button(buttonLabel("System.Button.Cancel")).click();
+ }
+
+ private SWTBotShell locationShell(SWTBot bot) {
+ SWTBotShell shell =
+ bot.shell(
+ BaseMessages.getString(
+ PipelineUnitTestSetLocationDialog.class,
+ "PipelineUnitTestSetLocationDialog.Shell.Title"));
+ shell.activate();
+ return shell;
+ }
+
+ private PipelineUnitTestSetLocationDialog
newDialog(org.eclipse.swt.widgets.Shell parent) {
+ IVariables variables = new Variables();
+ DataSet dataSet = dataSet();
+ IHopMetadataProvider metadataProvider = metadataProvider(dataSet);
+
+ PipelineUnitTestSetLocation location = new PipelineUnitTestSetLocation();
+ location.setTransformName(MAPPED_TRANSFORM);
+ location.setDataSetName(DATA_SET_NAME);
+
+ return new PipelineUnitTestSetLocationDialog(
+ parent,
+ variables,
+ metadataProvider,
+ location,
+ List.of(dataSet),
+ TRANSFORM_NAMES,
+ this::resolveFields);
+ }
+
+ /** Stands in for {@code PipelineMeta.getTransformFields()}, counting every
call. */
+ private IRowMeta resolveFields(String transformName) {
+ resolutions.computeIfAbsent(transformName, name -> new
AtomicInteger()).incrementAndGet();
+ IRowMeta rowMeta = new RowMeta();
+ rowMeta.addValueMeta(new ValueMetaString("id"));
+ rowMeta.addValueMeta(new ValueMetaString("name"));
+ return rowMeta;
+ }
+
+ private static DataSet dataSet() {
+ DataSet dataSet = new DataSet();
+ dataSet.setName(DATA_SET_NAME);
+ dataSet.getFields().add(new DataSetField("id", IValueMeta.TYPE_STRING, 50,
-1, null, null));
+ dataSet.getFields().add(new DataSetField("name", IValueMeta.TYPE_STRING,
50, -1, null, null));
+ return dataSet;
+ }
+
+ private static IHopMetadataProvider metadataProvider(DataSet dataSet) {
+ try {
+ MemoryMetadataProvider provider = new MemoryMetadataProvider();
+ provider.getSerializer(DataSet.class).save(dataSet);
+ return provider;
+ } catch (Exception e) {
+ throw new IllegalStateException("Unable to prepare the data set
metadata", e);
+ }
+ }
+}
diff --git
a/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMeta.java
b/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMeta.java
index 30dc063e60..882f789e56 100644
---
a/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMeta.java
+++
b/plugins/transforms/tableinput/src/main/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMeta.java
@@ -180,6 +180,15 @@ public class TableInputMeta extends
BaseTransformMeta<TableInput, TableInputData
e);
}
+ // The connection name can be a variable which isn't set at design time.
Without a connection
+ // we can't determine any fields, so report it instead of failing with an
NPE further down.
+ //
+ if (databaseMeta == null) {
+ throw new HopTransformException(
+ BaseMessages.getString(
+ PKG, "TableInputMeta.Exception.ConnectionNotFound",
variables.resolve(connection)));
+ }
+
Database db = new Database(loggingObject, variables, databaseMeta);
super.databases = new Database[] {db}; // keep track of it for canceling
purposes...
diff --git
a/plugins/transforms/tableinput/src/main/resources/org/apache/hop/pipeline/transforms/tableinput/messages/messages_en_US.properties
b/plugins/transforms/tableinput/src/main/resources/org/apache/hop/pipeline/transforms/tableinput/messages/messages_en_US.properties
index 30d38565dc..93d4510ee1 100644
---
a/plugins/transforms/tableinput/src/main/resources/org/apache/hop/pipeline/transforms/tableinput/messages/messages_en_US.properties
+++
b/plugins/transforms/tableinput/src/main/resources/org/apache/hop/pipeline/transforms/tableinput/messages/messages_en_US.properties
@@ -101,5 +101,6 @@ TableInputSql.Exception.UnclosedNamedParameter=Unclosed
named parameter in SQL.
TableInputSql.Exception.EmptyNamedParameter=Empty named parameter in SQL.
TableInputSql.Exception.MixedPlaceholders=SQL cannot mix named parameters with
positional question-mark placeholders. Use one style only.
TableInputMeta.Exception.CouldNotLoadSqlFromFile=Could not load SQL from file:
{0}
+TableInputMeta.Exception.ConnectionNotFound=Unable to find database connection
''{0}''. Check the connection name, it may contain a variable which isn''t set.
TableInputMeta.keyword=sql,query,database,select,jdbc
System.FileType.AllFiles=All files
diff --git
a/plugins/transforms/tableinput/src/test/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMetaTest.java
b/plugins/transforms/tableinput/src/test/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMetaTest.java
index 657734c41c..34b4e26f9d 100644
---
a/plugins/transforms/tableinput/src/test/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMetaTest.java
+++
b/plugins/transforms/tableinput/src/test/java/org/apache/hop/pipeline/transforms/tableinput/TableInputMetaTest.java
@@ -26,6 +26,7 @@ import java.util.List;
import java.util.Objects;
import org.apache.hop.core.HopEnvironment;
import org.apache.hop.core.ICheckResult;
+import org.apache.hop.core.exception.HopTransformException;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.row.RowMeta;
@@ -374,6 +375,29 @@ class TableInputMetaTest {
&& r.getText().contains("3 fields")));
}
+ @Test
+ void getFieldsReportsUnresolvedConnectionVariable() {
+ // A connection name which is a variable that isn't set at design time
used to fail with a
+ // NullPointerException deep inside the Database object. See issue #8203.
+ //
+ TableInputMeta meta = new TableInputMeta();
+ meta.setConnection("${connection_name}");
+ meta.setSql("SELECT * FROM t");
+
+ HopTransformException e =
+ Assertions.assertThrows(
+ HopTransformException.class,
+ () ->
+ meta.getFields(
+ new RowMeta(),
+ "Table input",
+ null,
+ null,
+ new Variables(),
+ new MemoryMetadataProvider()));
+ Assertions.assertTrue(e.getMessage().contains("${connection_name}"),
e.getMessage());
+ }
+
private static TableInputMeta namedParameterMeta() {
TableInputMeta meta = new TableInputMeta();
meta.setConnection("h2");