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 db8ea14d3f [Bug] Database perspective keeps old metadata around, fixes
#8375 (#8376)
db8ea14d3f is described below
commit db8ea14d3f0995c30b8123cbd8583babb48929fe
Author: Matt Casters <[email protected]>
AuthorDate: Tue Sep 15 18:24:24 2026 +0200
[Bug] Database perspective keeps old metadata around, fixes #8375 (#8376)
- Implicitly reload metadata when connecting to a database or executing
queries in the SQL editor tab
- If the connection no longer exists in the metadata provider, remove it
from the tree and abort
- Fetch the latest metadata from the metadata serializer and update the
connection state and open tabs
- Synchronize open DatabaseSqlEditorTab and DatabaseTableInfoTab with the
fresh DatabaseMeta
- Add DatabaseWorkbenchReloadMetaTest unit tests
---
.../perspective/database/DatabaseSqlEditorTab.java | 18 +-
.../perspective/database/DatabaseTableInfoTab.java | 20 +-
.../perspective/database/DatabaseWorkbench.java | 259 +++++++++++++++++----
.../database/messages/messages_en_US.properties | 1 +
.../database/DatabaseWorkbenchReloadMetaTest.java | 185 +++++++++++++++
5 files changed, 432 insertions(+), 51 deletions(-)
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
index 34168870fa..d21c223586 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseSqlEditorTab.java
@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.Getter;
+import lombok.Setter;
import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.hop.core.Const;
@@ -86,7 +87,7 @@ public class DatabaseSqlEditorTab implements
IHopFileTypeHandler {
private final IDatabaseWorkbenchHost host;
private final DatabaseWorkbench workbench;
- @Getter private final DatabaseMeta databaseMeta;
+ @Getter @Setter private DatabaseMeta databaseMeta;
@Getter private final Composite control;
@Getter private CTabItem tabItem;
@@ -376,6 +377,21 @@ public class DatabaseSqlEditorTab implements
IHopFileTypeHandler {
if (range == null || range.isBlank()) {
return;
}
+ if (databaseMeta != null) {
+ DatabaseMeta freshMeta =
workbench.reloadConnectionMeta(databaseMeta.getName());
+ if (freshMeta == null) {
+ new ErrorDialog(
+ host.getShell(),
+ BaseMessages.getString(PKG, "DatabasePerspective.Error.Title"),
+ BaseMessages.getString(
+ PKG, "DatabasePerspective.Error.ConnectionNotFound",
databaseMeta.getName()),
+ new HopException(
+ BaseMessages.getString(
+ PKG, "DatabasePerspective.Error.ConnectionNotFound",
databaseMeta.getName())));
+ return;
+ }
+ this.databaseMeta = freshMeta;
+ }
workbench.ensureConnectedForExecute(databaseMeta, () ->
runSqlStatements(range, caretInEditor));
}
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseTableInfoTab.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseTableInfoTab.java
index 308a8c5443..90ffb43cdc 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseTableInfoTab.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseTableInfoTab.java
@@ -27,10 +27,12 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import lombok.Getter;
+import lombok.Setter;
import org.apache.hop.core.Const;
import org.apache.hop.core.database.Database;
import org.apache.hop.core.database.DatabaseMeta;
import org.apache.hop.core.database.DatabaseObjectDdl;
+import org.apache.hop.core.exception.HopException;
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.core.row.IValueMeta;
import org.apache.hop.core.util.Utils;
@@ -38,6 +40,7 @@ import org.apache.hop.core.variables.IVariables;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.FormDataBuilder;
import org.apache.hop.ui.core.PropsUi;
+import org.apache.hop.ui.core.dialog.ErrorDialog;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.widget.ColumnInfo;
import org.apache.hop.ui.core.widget.TableView;
@@ -64,7 +67,7 @@ public class DatabaseTableInfoTab implements
IHopFileTypeHandler {
private final IDatabaseWorkbenchHost host;
private final DatabaseWorkbench workbench;
- @Getter private final DatabaseMeta databaseMeta;
+ @Getter @Setter private DatabaseMeta databaseMeta;
@Getter private final String schemaName;
@Getter private final String tableName;
@Getter private final DatabaseTreeNode.Kind kind;
@@ -175,6 +178,21 @@ public class DatabaseTableInfoTab implements
IHopFileTypeHandler {
}
public void loadDetails() {
+ if (databaseMeta != null) {
+ DatabaseMeta freshMeta =
workbench.reloadConnectionMeta(databaseMeta.getName());
+ if (freshMeta == null) {
+ new ErrorDialog(
+ host.getShell(),
+ BaseMessages.getString(PKG, "DatabasePerspective.Error.Title"),
+ BaseMessages.getString(
+ PKG, "DatabasePerspective.Error.ConnectionNotFound",
databaseMeta.getName()),
+ new HopException(
+ BaseMessages.getString(
+ PKG, "DatabasePerspective.Error.ConnectionNotFound",
databaseMeta.getName())));
+ return;
+ }
+ this.databaseMeta = freshMeta;
+ }
String qualified =
databaseMeta.getQuotedSchemaTableCombination(host.getVariables(),
schemaName, tableName);
String description =
diff --git
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
index e61f2d681c..0e9f94a03d 100644
---
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
+++
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbench.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import org.apache.hop.core.Const;
import org.apache.hop.core.database.Catalog;
import org.apache.hop.core.database.Database;
@@ -38,6 +39,7 @@ import org.apache.hop.core.search.SearchMatcher;
import org.apache.hop.core.util.Utils;
import org.apache.hop.core.variables.IVariables;
import org.apache.hop.i18n.BaseMessages;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
import org.apache.hop.ui.core.ConstUi;
import org.apache.hop.ui.core.FormDataBuilder;
import org.apache.hop.ui.core.PropsUi;
@@ -228,30 +230,32 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
horizontalSash.setWeights(22, 78);
SashFormMemory.persist(horizontalSash, "database-workbench-tree-width",
22, 78);
- host.getHopGui()
- .getEventsHandler()
- .addEventListener(
- eventListenerId,
- e -> host.asyncExec(this::reloadConnections),
- HopGuiEvents.MetadataChanged.name(),
- HopGuiEvents.MetadataCreated.name(),
- HopGuiEvents.MetadataDeleted.name());
- host.getHopGui()
- .getEventsHandler()
- .addEventListener(
- eventListenerId + "-project",
- e ->
- host.asyncExec(
- () -> {
- closeSqlEditorTabs();
- operationsPanel.cancelAll();
- operationsPanel.clearAll();
- reloadConnections();
- if (DatabaseSqlTabMemory.isOwner(this)) {
- DatabaseSqlTabMemory.restore(this);
- }
- }),
- HopGuiEvents.ProjectActivated.name());
+ if (host.getHopGui() != null && host.getHopGui().getEventsHandler() !=
null) {
+ host.getHopGui()
+ .getEventsHandler()
+ .addEventListener(
+ eventListenerId,
+ e -> host.asyncExec(this::reloadConnections),
+ HopGuiEvents.MetadataChanged.name(),
+ HopGuiEvents.MetadataCreated.name(),
+ HopGuiEvents.MetadataDeleted.name());
+ host.getHopGui()
+ .getEventsHandler()
+ .addEventListener(
+ eventListenerId + "-project",
+ e ->
+ host.asyncExec(
+ () -> {
+ closeSqlEditorTabs();
+ operationsPanel.cancelAll();
+ operationsPanel.clearAll();
+ reloadConnections();
+ if (DatabaseSqlTabMemory.isOwner(this)) {
+ DatabaseSqlTabMemory.restore(this);
+ }
+ }),
+ HopGuiEvents.ProjectActivated.name());
+ }
addDisposeListener(
e -> {
@@ -262,8 +266,10 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
if (wasOwner) {
DatabaseSqlTabMemory.restoreIntoRemaining(this);
}
-
host.getHopGui().getEventsHandler().removeEventListeners(eventListenerId);
-
host.getHopGui().getEventsHandler().removeEventListeners(eventListenerId +
"-project");
+ if (host.getHopGui() != null && host.getHopGui().getEventsHandler()
!= null) {
+
host.getHopGui().getEventsHandler().removeEventListeners(eventListenerId);
+
host.getHopGui().getEventsHandler().removeEventListeners(eventListenerId +
"-project");
+ }
});
DatabaseSqlTabMemory.register(this);
@@ -297,6 +303,7 @@ public class DatabaseWorkbench extends Composite implements
TabClosable {
state.setDatabaseMeta(meta);
}
connections.put(meta.getName(), state);
+ updateTabsDatabaseMeta(meta);
}
} catch (Exception e) {
new ErrorDialog(
@@ -308,6 +315,106 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
rebuildTree();
}
+ public void updateTabsDatabaseMeta(DatabaseMeta freshMeta) {
+ updateTabsDatabaseMeta(freshMeta, items);
+ }
+
+ static void updateTabsDatabaseMeta(DatabaseMeta freshMeta,
List<TabItemHandler> items) {
+ if (freshMeta == null || items == null) {
+ return;
+ }
+ for (TabItemHandler item : items) {
+ if (item.getTypeHandler() instanceof DatabaseSqlEditorTab sqlTab) {
+ if (sqlTab.getDatabaseMeta() != null
+ && Objects.equals(sqlTab.getDatabaseMeta().getName(),
freshMeta.getName())) {
+ sqlTab.setDatabaseMeta(freshMeta);
+ }
+ } else if (item.getTypeHandler() instanceof DatabaseTableInfoTab
tableTab) {
+ if (tableTab.getDatabaseMeta() != null
+ && Objects.equals(tableTab.getDatabaseMeta().getName(),
freshMeta.getName())) {
+ tableTab.setDatabaseMeta(freshMeta);
+ }
+ }
+ }
+ }
+
+ private void safeRebuildTree() {
+ if (isDisposed()) {
+ return;
+ }
+ if (Display.getCurrent() != null) {
+ rebuildTree();
+ } else {
+ host.asyncExec(this::rebuildTree);
+ }
+ }
+
+ public DatabaseMeta reloadConnectionMeta(String connectionName) {
+ return reloadConnectionMeta(connectionName, host, connections, items,
this::safeRebuildTree);
+ }
+
+ static DatabaseMeta reloadConnectionMeta(
+ String connectionName,
+ IDatabaseWorkbenchHost host,
+ Map<String, DatabaseConnectionState> connections,
+ List<TabItemHandler> items,
+ Runnable onTreeChanged) {
+ if (Utils.isEmpty(connectionName)) {
+ return null;
+ }
+ if (host == null || host.getMetadataProvider() == null) {
+ DatabaseConnectionState state = connections == null ? null :
connections.get(connectionName);
+ return state == null ? null : state.getDatabaseMeta();
+ }
+ try {
+ IHopMetadataSerializer<DatabaseMeta> serializer =
+ host.getMetadataProvider().getSerializer(DatabaseMeta.class);
+ if (!serializer.exists(connectionName)) {
+ if (connections != null) {
+ connections.remove(connectionName);
+ }
+ if (onTreeChanged != null) {
+ onTreeChanged.run();
+ }
+ return null;
+ }
+ DatabaseMeta freshMeta = serializer.load(connectionName);
+ if (freshMeta == null) {
+ if (connections != null) {
+ connections.remove(connectionName);
+ }
+ if (onTreeChanged != null) {
+ onTreeChanged.run();
+ }
+ return null;
+ }
+ if (connections != null) {
+ DatabaseConnectionState state = connections.get(connectionName);
+ if (state == null) {
+ state = new DatabaseConnectionState(freshMeta);
+ connections.put(connectionName, state);
+ if (onTreeChanged != null) {
+ onTreeChanged.run();
+ }
+ } else {
+ state.setDatabaseMeta(freshMeta);
+ }
+ }
+ updateTabsDatabaseMeta(freshMeta, items);
+ return freshMeta;
+ } catch (Exception e) {
+ if (host != null && host.getShell() != null) {
+ new ErrorDialog(
+ host.getShell(),
+ BaseMessages.getString(PKG, "DatabasePerspective.Error.Title"),
+ BaseMessages.getString(PKG,
"DatabasePerspective.Error.LoadConnections"),
+ e);
+ }
+ DatabaseConnectionState state = connections == null ? null :
connections.get(connectionName);
+ return state == null ? null : state.getDatabaseMeta();
+ }
+ }
+
public void clearSearchFilter() {
if (searchText != null && !searchText.isDisposed()) {
searchText.setText("");
@@ -767,24 +874,38 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
* thread after a successful connect.
*/
public void connect(DatabaseConnectionState state, Runnable afterConnected) {
- DatabaseMeta meta = state.getDatabaseMeta();
+ if (state == null || state.getDatabaseMeta() == null) {
+ return;
+ }
+ String connectionName = state.getDatabaseMeta().getName();
+ DatabaseMeta freshMeta = reloadConnectionMeta(connectionName);
+ if (freshMeta == null) {
+ return;
+ }
+ DatabaseConnectionState targetState = connections.get(freshMeta.getName());
+ if (targetState == null) {
+ targetState = state;
+ }
+ targetState.setDatabaseMeta(freshMeta);
+ final DatabaseConnectionState connectState = targetState;
String description =
- BaseMessages.getString(PKG, "DatabasePerspective.Operation.Connect",
meta.getName());
+ BaseMessages.getString(PKG, "DatabasePerspective.Operation.Connect",
freshMeta.getName());
runOperation(
description,
- meta.getName(),
+ freshMeta.getName(),
operation -> {
- DatabaseMetaInformation info = new
DatabaseMetaInformation(host.getVariables(), meta);
+ DatabaseMetaInformation info =
+ new DatabaseMetaInformation(host.getVariables(), freshMeta);
info.getData(host.getLoggingObject(), operation.newMonitor());
if (operation.isCancelled()) {
return;
}
host.asyncExec(
() -> {
- state.setInformation(info);
- state.setConnected(true);
+ connectState.setInformation(info);
+ connectState.setConnected(true);
rebuildTree();
- selectConnection(meta.getName());
+ selectConnection(freshMeta.getName());
if (afterConnected != null) {
afterConnected.run();
}
@@ -803,6 +924,9 @@ public class DatabaseWorkbench extends Composite implements
TabClosable {
return false;
}
DatabaseConnectionState state = ensureConnection(meta);
+ if (state == null) {
+ return false;
+ }
if (state.isConnected()) {
afterConnected.run();
return true;
@@ -902,17 +1026,19 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
if (state == null) {
return;
}
+ DatabaseMeta freshMeta =
reloadConnectionMeta(state.getDatabaseMeta().getName());
+ if (freshMeta == null) {
+ return;
+ }
DatabaseTreeNode node = selectedNode();
String sql = "";
if (node != null && node.isTableLike()) {
String qualified =
- state
- .getDatabaseMeta()
- .getQuotedSchemaTableCombination(
- host.getVariables(), node.getSchemaName(),
node.getObjectName());
+ freshMeta.getQuotedSchemaTableCombination(
+ host.getVariables(), node.getSchemaName(), node.getObjectName());
sql = "SELECT * FROM " + qualified;
}
- openSqlTab(state.getDatabaseMeta(), sql, null, sql, false);
+ openSqlTab(freshMeta, sql, null, sql, false);
}
@GuiToolbarElement(
@@ -932,7 +1058,11 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
if (node == null || state == null || !node.isTableLike()) {
return;
}
- DatabaseMeta meta = state.getDatabaseMeta();
+ DatabaseMeta freshMeta =
reloadConnectionMeta(state.getDatabaseMeta().getName());
+ if (freshMeta == null) {
+ return;
+ }
+ DatabaseMeta meta = freshMeta;
String qualified =
meta.getQuotedSchemaTableCombination(
host.getVariables(), node.getSchemaName(), node.getObjectName());
@@ -971,7 +1101,11 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
if (node == null || state == null || !node.isTableLike()) {
return;
}
- DatabaseMeta meta = state.getDatabaseMeta();
+ DatabaseMeta freshMeta =
reloadConnectionMeta(state.getDatabaseMeta().getName());
+ if (freshMeta == null) {
+ return;
+ }
+ DatabaseMeta meta = freshMeta;
String sql =
previewSelectSql(
meta,
@@ -1013,8 +1147,11 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
if (node == null || state == null || !node.isTableLike()) {
return;
}
- openTableInfo(
- state.getDatabaseMeta(), node.getSchemaName(), node.getObjectName(),
node.getKind());
+ DatabaseMeta freshMeta =
reloadConnectionMeta(state.getDatabaseMeta().getName());
+ if (freshMeta == null) {
+ return;
+ }
+ openTableInfo(freshMeta, node.getSchemaName(), node.getObjectName(),
node.getKind());
}
@GuiToolbarElement(
@@ -1133,21 +1270,45 @@ public class DatabaseWorkbench extends Composite
implements TabClosable {
return;
}
DatabaseConnectionState state = ensureConnection(meta);
- selectConnection(meta.getName());
- openSqlTab(meta, Const.NVL(sql, ""), null, Const.NVL(sql, ""), true);
+ if (state == null) {
+ return;
+ }
+ DatabaseMeta effectiveMeta = state.getDatabaseMeta();
+ selectConnection(effectiveMeta.getName());
+ openSqlTab(effectiveMeta, Const.NVL(sql, ""), null, Const.NVL(sql, ""),
true);
if (!state.isConnected()) {
connect(state);
}
}
DatabaseConnectionState ensureConnection(DatabaseMeta meta) {
- DatabaseConnectionState state = connections.get(meta.getName());
+ return ensureConnection(meta, host, connections, items,
this::safeRebuildTree);
+ }
+
+ static DatabaseConnectionState ensureConnection(
+ DatabaseMeta meta,
+ IDatabaseWorkbenchHost host,
+ Map<String, DatabaseConnectionState> connections,
+ List<TabItemHandler> items,
+ Runnable onTreeChanged) {
+ if (meta == null || connections == null) {
+ return null;
+ }
+ DatabaseMeta freshMeta =
+ reloadConnectionMeta(meta.getName(), host, connections, items,
onTreeChanged);
+ if (freshMeta == null && host != null && host.getMetadataProvider() !=
null) {
+ return null;
+ }
+ DatabaseMeta effectiveMeta = freshMeta != null ? freshMeta : meta;
+ DatabaseConnectionState state = connections.get(effectiveMeta.getName());
if (state == null) {
- state = new DatabaseConnectionState(meta);
- connections.put(meta.getName(), state);
- rebuildTree();
+ state = new DatabaseConnectionState(effectiveMeta);
+ connections.put(effectiveMeta.getName(), state);
+ if (onTreeChanged != null) {
+ onTreeChanged.run();
+ }
} else {
- state.setDatabaseMeta(meta);
+ state.setDatabaseMeta(effectiveMeta);
}
return state;
}
diff --git
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/database/messages/messages_en_US.properties
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/database/messages/messages_en_US.properties
index bba654a925..cb8c4fbfab 100644
---
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/database/messages/messages_en_US.properties
+++
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/database/messages/messages_en_US.properties
@@ -104,3 +104,4 @@ DatabasePerspective.OpenInDatabase.Message=Select the
database connection to run
DatabasePerspective.OpenInDatabase.Tooltip=Open this SQL file in the Database
perspective
DatabasePerspective.Dialog.Title=Database
DatabasePerspective.Menu.Tools.Window=Database window
+DatabasePerspective.Error.ConnectionNotFound=Database connection ''{0}'' no
longer exists.
diff --git
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchReloadMetaTest.java
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchReloadMetaTest.java
new file mode 100644
index 0000000000..71bc2eb7c5
--- /dev/null
+++
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchReloadMetaTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.hopgui.perspective.database;
+
+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.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.hop.core.database.DatabaseMeta;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.metadata.api.IHopMetadataSerializer;
+import org.apache.hop.ui.hopgui.perspective.TabItemHandler;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class DatabaseWorkbenchReloadMetaTest {
+
+ private IDatabaseWorkbenchHost host;
+ private IHopMetadataProvider metadataProvider;
+ private IHopMetadataSerializer<DatabaseMeta> serializer;
+
+ @BeforeEach
+ @SuppressWarnings("unchecked")
+ void setUp() throws Exception {
+ metadataProvider = mock(IHopMetadataProvider.class);
+ serializer = (IHopMetadataSerializer<DatabaseMeta>)
mock(IHopMetadataSerializer.class);
+
when(metadataProvider.getSerializer(DatabaseMeta.class)).thenReturn(serializer);
+
+ host = mock(IDatabaseWorkbenchHost.class);
+ when(host.getMetadataProvider()).thenReturn(metadataProvider);
+ }
+
+ @Test
+ void reloadConnectionMetaReturnsNullForEmpty() {
+ Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+ assertNull(DatabaseWorkbench.reloadConnectionMeta(null, host, connections,
List.of(), null));
+ assertNull(DatabaseWorkbench.reloadConnectionMeta("", host, connections,
List.of(), null));
+ }
+
+ @Test
+ void reloadConnectionMetaRemovesDeletedConnection() throws Exception {
+ Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getName()).thenReturn("deleted-conn");
+ connections.put("deleted-conn", new DatabaseConnectionState(meta));
+
+ when(serializer.exists("deleted-conn")).thenReturn(false);
+
+ AtomicBoolean treeChanged = new AtomicBoolean(false);
+ DatabaseMeta result =
+ DatabaseWorkbench.reloadConnectionMeta(
+ "deleted-conn", host, connections, List.of(), () ->
treeChanged.set(true));
+
+ assertNull(result);
+ assertFalse(connections.containsKey("deleted-conn"));
+ assertTrue(treeChanged.get());
+ }
+
+ @Test
+ void reloadConnectionMetaUpdatesExistingStateAndOpenTabs() throws Exception {
+ Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+ DatabaseMeta staleMeta = mock(DatabaseMeta.class);
+ when(staleMeta.getName()).thenReturn("my-conn");
+ when(staleMeta.getHostname()).thenReturn("old-host");
+ connections.put("my-conn", new DatabaseConnectionState(staleMeta));
+
+ DatabaseMeta freshMeta = mock(DatabaseMeta.class);
+ when(freshMeta.getName()).thenReturn("my-conn");
+ when(freshMeta.getHostname()).thenReturn("new-host");
+
+ when(serializer.exists("my-conn")).thenReturn(true);
+ when(serializer.load("my-conn")).thenReturn(freshMeta);
+
+ DatabaseSqlEditorTab sqlTab = mock(DatabaseSqlEditorTab.class);
+ when(sqlTab.getDatabaseMeta()).thenReturn(staleMeta);
+ TabItemHandler sqlHandler = mock(TabItemHandler.class);
+ when(sqlHandler.getTypeHandler()).thenReturn(sqlTab);
+
+ DatabaseTableInfoTab tableTab = mock(DatabaseTableInfoTab.class);
+ when(tableTab.getDatabaseMeta()).thenReturn(staleMeta);
+ TabItemHandler tableHandler = mock(TabItemHandler.class);
+ when(tableHandler.getTypeHandler()).thenReturn(tableTab);
+
+ DatabaseMeta result =
+ DatabaseWorkbench.reloadConnectionMeta(
+ "my-conn", host, connections, List.of(sqlHandler, tableHandler),
null);
+
+ assertSame(freshMeta, result);
+ assertSame(freshMeta, connections.get("my-conn").getDatabaseMeta());
+ verify(sqlTab).setDatabaseMeta(freshMeta);
+ verify(tableTab).setDatabaseMeta(freshMeta);
+ }
+
+ @Test
+ void ensureConnectionReturnsNullWhenDeleted() throws Exception {
+ Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+ DatabaseMeta meta = mock(DatabaseMeta.class);
+ when(meta.getName()).thenReturn("deleted-conn");
+ connections.put("deleted-conn", new DatabaseConnectionState(meta));
+
+ when(serializer.exists("deleted-conn")).thenReturn(false);
+
+ DatabaseConnectionState state =
+ DatabaseWorkbench.ensureConnection(meta, host, connections, List.of(),
null);
+
+ assertNull(state);
+ assertFalse(connections.containsKey("deleted-conn"));
+ }
+
+ @Test
+ void ensureConnectionReturnsFreshStateWhenExists() throws Exception {
+ Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+ DatabaseMeta staleMeta = mock(DatabaseMeta.class);
+ when(staleMeta.getName()).thenReturn("conn1");
+ when(staleMeta.getHostname()).thenReturn("old-host");
+ connections.put("conn1", new DatabaseConnectionState(staleMeta));
+
+ DatabaseMeta freshMeta = mock(DatabaseMeta.class);
+ when(freshMeta.getName()).thenReturn("conn1");
+ when(freshMeta.getHostname()).thenReturn("new-host");
+
+ when(serializer.exists("conn1")).thenReturn(true);
+ when(serializer.load("conn1")).thenReturn(freshMeta);
+
+ DatabaseConnectionState state =
+ DatabaseWorkbench.ensureConnection(staleMeta, host, connections,
List.of(), null);
+
+ assertNotNull(state);
+ assertSame(freshMeta, state.getDatabaseMeta());
+ assertEquals("new-host", state.getDatabaseMeta().getHostname());
+ }
+
+ @Test
+ void updateTabsDatabaseMetaUpdatesMatchingTabsOnly() {
+ DatabaseMeta freshMeta = mock(DatabaseMeta.class);
+ when(freshMeta.getName()).thenReturn("match");
+
+ DatabaseMeta matchMeta = mock(DatabaseMeta.class);
+ when(matchMeta.getName()).thenReturn("match");
+
+ DatabaseMeta otherMeta = mock(DatabaseMeta.class);
+ when(otherMeta.getName()).thenReturn("other");
+
+ DatabaseSqlEditorTab matchTab = mock(DatabaseSqlEditorTab.class);
+ when(matchTab.getDatabaseMeta()).thenReturn(matchMeta);
+ TabItemHandler matchHandler = mock(TabItemHandler.class);
+ when(matchHandler.getTypeHandler()).thenReturn(matchTab);
+
+ DatabaseSqlEditorTab otherTab = mock(DatabaseSqlEditorTab.class);
+ when(otherTab.getDatabaseMeta()).thenReturn(otherMeta);
+ TabItemHandler otherHandler = mock(TabItemHandler.class);
+ when(otherHandler.getTypeHandler()).thenReturn(otherTab);
+
+ DatabaseWorkbench.updateTabsDatabaseMeta(freshMeta, List.of(matchHandler,
otherHandler));
+
+ verify(matchTab).setDatabaseMeta(freshMeta);
+ verify(otherTab, never()).setDatabaseMeta(freshMeta);
+ }
+}