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 87a70d25b2 Issue #8361 : Link RDBMS metadata editor with the Database 
perspective (#8405)
87a70d25b2 is described below

commit 87a70d25b2c6c61645abea718617ef48cdb3034b
Author: Matt Casters <[email protected]>
AuthorDate: Wed Sep 16 09:34:09 2026 +0200

    Issue #8361 : Link RDBMS metadata editor with the Database perspective 
(#8405)
    
    Add Open in Database on the connection editor and Edit connection on the
    Database workbench. Explorer SQL/DDL/Truncate use the floating workbench.
    SqlEditor remains as a public API for other callers.
    
    fixes #8361
---
 .../ROOT/pages/hop-gui/perspective-database.adoc   |  5 +-
 .../pages/metadata-types/rdbms-connection.adoc     |  1 +
 .../hop/ui/core/database/DatabaseMetaEditor.java   | 75 +++++++++++++++-------
 .../database/dialog/DatabaseExplorerDialog.java    | 42 ++++++------
 .../main/java/org/apache/hop/ui/hopgui/HopGui.java |  5 ++
 .../perspective/database/DatabasePerspective.java  | 13 ++++
 .../perspective/database/DatabaseWorkbench.java    | 54 +++++++++++++++-
 .../database/DatabaseWorkbenchViews.java           | 34 ++++++++++
 .../dialog/messages/messages_en_US.properties      |  1 +
 .../database/messages/messages_en_US.properties    |  4 ++
 .../database/messages/messages_en_US.properties    |  2 +
 .../perspective/DisabledPerspectivesTest.java      |  1 +
 .../database/DatabaseWorkbenchReloadMetaTest.java  | 24 +++++--
 .../database/DatabaseWorkbenchViewsTest.java       | 31 +++++++++
 14 files changed, 242 insertions(+), 50 deletions(-)

diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
index 07f745c61c..00ecbcc45f 100644
--- a/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
+++ b/docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-database.adoc
@@ -40,6 +40,7 @@ Toolbar:
 * *Connect* — load schemas and their tables (background; does not freeze Hop 
Gui)
 * *Disconnect* — drop the loaded schema list
 * *Refresh* — reload the selected connected connection, or the connection list
+* *Edit connection* — open this connection in the Metadata perspective (or a 
dialog if that perspective is disabled)
 * *Open SQL editor* — new SQL tab for the selected connection (`SELECT * FROM 
table` when a table is selected)
 * *Generate DDL* — `CREATE TABLE` for the selected table, opened in a SQL tab
 * *Preview table* — run `SELECT` with a dialect-aware row limit (the **Query 
row limit** in Configuration → Plugins → Database Perspective, default 1000)
@@ -88,7 +89,9 @@ 
image::hop-gui/database/database-perspective-popup-dialog.png[The floating Datab
 
 Each placement has its own workbench instance (connection tree, SQL tabs, 
operations). The perspective stays available with `Ctrl+Shift+D`. SQL editor 
tabs (connection, file or buffer, unsaved edits) are remembered per project 
when you close the window or dock and when you restart Hop Gui. Query results 
are not stored.
 
-**SQL** on a table-output (or bulk-loader, lookup, update, …) transform dialog 
opens this floating window on the transform's connection: the schema/table tree 
refreshes in the background while you edit and run the generated SQL. The same 
happens from the database explorer (Open SQL, Generate DDL, Truncate).
+**SQL** on a table-output (or bulk-loader, lookup, update, …) transform dialog 
opens this floating window on the transform's connection: the schema/table tree 
refreshes in the background while you edit and run the generated SQL. The same 
happens from the database explorer (Open SQL, Generate DDL, Truncate). The 
explorer toolbar also has *Open in Database perspective*, which closes the 
explorer and switches to this perspective on the explorer's connection.
+
+On the relational database connection metadata editor, *Open in Database* does 
the same from the other direction: it activates this perspective on that 
connection so you can explore and query immediately.
 
 == See also
 
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/rdbms-connection.adoc 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/rdbms-connection.adoc
index dac163b2e2..78e306971d 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/rdbms-connection.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/rdbms-connection.adoc
@@ -34,6 +34,7 @@ Transforms and actions store the *name* of a relational 
database connection. Whe
 
 
image::hop-gui/validate-database-connections-on-save-warning-dialog.png[Database
 connections save warning,width="90%"]
 
+The connection editor has *Explore* (the classic table picker) and *Open in 
Database* (the xref:hop-gui/perspective-database.adoc[Database perspective], to 
browse schemas and run SQL). From that perspective, *Edit connection* opens 
this metadata editor.
 
 == Related Plugins
 
diff --git 
a/ui/src/main/java/org/apache/hop/ui/core/database/DatabaseMetaEditor.java 
b/ui/src/main/java/org/apache/hop/ui/core/database/DatabaseMetaEditor.java
index 94865c37cb..2bdcda2614 100644
--- a/ui/src/main/java/org/apache/hop/ui/core/database/DatabaseMetaEditor.java
+++ b/ui/src/main/java/org/apache/hop/ui/core/database/DatabaseMetaEditor.java
@@ -64,6 +64,7 @@ import org.apache.hop.ui.core.widget.NamingSchemeTypes;
 import org.apache.hop.ui.core.widget.TableView;
 import org.apache.hop.ui.core.widget.TextVar;
 import org.apache.hop.ui.hopgui.HopGui;
+import org.apache.hop.ui.hopgui.perspective.database.DatabaseWorkbenchViews;
 import org.apache.hop.ui.hopgui.perspective.metadata.MetadataPerspective;
 import org.apache.hop.ui.util.HelpUtils;
 import org.eclipse.swt.SWT;
@@ -1106,29 +1107,53 @@ public class DatabaseMetaEditor extends 
MetadataEditor<DatabaseMeta> {
   }
 
   private void explore() {
+    if (!canExploreDatabase()) {
+      return;
+    }
+    DatabaseMeta meta = new DatabaseMeta();
+    getWidgetsContent(meta);
+    try {
+      DatabaseExplorerDialog dialog =
+          new DatabaseExplorerDialog(
+              getShell(),
+              SWT.NONE,
+              manager.getVariables(),
+              meta,
+              manager.getSerializer().loadAll(),
+              true,
+              true);
+      dialog.open();
+    } catch (Exception e) {
+      new ErrorDialog(getShell(), "Error", "Error exploring database", e);
+    }
+  }
+
+  private void openInDatabase() {
+    if (!canExploreDatabase()) {
+      return;
+    }
+    DatabaseMeta meta = new DatabaseMeta();
+    getWidgetsContent(meta);
+    if (StringUtils.isBlank(meta.getName())) {
+      MessageBox box = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR);
+      box.setText(BaseMessages.getString(PKG, 
"DatabaseDialog.OpenInDatabase.NameRequired.Title"));
+      box.setMessage(
+          BaseMessages.getString(PKG, 
"DatabaseDialog.OpenInDatabase.NameRequired.Message"));
+      box.open();
+      return;
+    }
+    DatabaseWorkbenchViews.openInDatabase(hopGui, meta, "");
+  }
+
+  private boolean canExploreDatabase() {
     if (!getMetadata().isExploringDisabled()) {
-      DatabaseMeta meta = new DatabaseMeta();
-      getWidgetsContent(meta);
-      try {
-        DatabaseExplorerDialog dialog =
-            new DatabaseExplorerDialog(
-                getShell(),
-                SWT.NONE,
-                manager.getVariables(),
-                meta,
-                manager.getSerializer().loadAll(),
-                true,
-                true);
-        dialog.open();
-      } catch (Exception e) {
-        new ErrorDialog(getShell(), "Error", "Error exploring database", e);
-      }
-    } else {
-      MessageBox mb = new MessageBox(HopGui.getInstance().getShell(), SWT.OK | 
SWT.ICON_ERROR);
-      mb.setText(BaseMessages.getString(PKG, 
"DatabaseDialog.Exploring.Disabled.title"));
-      mb.setMessage(BaseMessages.getString(PKG, 
"DatabaseDialog.Exploring.Disabled.description"));
-      mb.open();
+      return true;
     }
+    MessageBox mb = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR);
+    mb.setText(BaseMessages.getString(PKG, 
"DatabaseDialog.Exploring.Disabled.title"));
+    mb.setMessage(BaseMessages.getString(PKG, 
"DatabaseDialog.Exploring.Disabled.description"));
+    mb.open();
+    return false;
   }
 
   private void onHelpDatabaseType() {
@@ -1506,11 +1531,17 @@ public class DatabaseMetaEditor extends 
MetadataEditor<DatabaseMeta> {
     wExplore.setText(BaseMessages.getString(PKG, 
"DatabaseDialog.button.Explore"));
     wExplore.addListener(SWT.Selection, e -> explore());
 
+    Button wOpenInDatabase = new Button(parent, SWT.PUSH);
+    wOpenInDatabase.setText(BaseMessages.getString(PKG, 
"DatabaseDialog.button.OpenInDatabase"));
+    wOpenInDatabase.setToolTipText(
+        BaseMessages.getString(PKG, 
"DatabaseDialog.button.OpenInDatabase.Tooltip"));
+    wOpenInDatabase.addListener(SWT.Selection, e -> openInDatabase());
+
     Button wTest = new Button(parent, SWT.PUSH);
     wTest.setText(BaseMessages.getString(PKG, "System.Button.Test"));
     wTest.addListener(SWT.Selection, e -> test());
 
-    return new Button[] {wGenerateVariables, wExplore, wTest};
+    return new Button[] {wGenerateVariables, wExplore, wOpenInDatabase, wTest};
   }
 
   /**
diff --git 
a/ui/src/main/java/org/apache/hop/ui/core/database/dialog/DatabaseExplorerDialog.java
 
b/ui/src/main/java/org/apache/hop/ui/core/database/dialog/DatabaseExplorerDialog.java
index 76ad966459..5973e93363 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/core/database/dialog/DatabaseExplorerDialog.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/core/database/dialog/DatabaseExplorerDialog.java
@@ -24,7 +24,6 @@ import java.util.List;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.hop.core.Const;
-import org.apache.hop.core.DbCache;
 import org.apache.hop.core.Props;
 import org.apache.hop.core.database.Catalog;
 import org.apache.hop.core.database.Database;
@@ -38,9 +37,7 @@ import 
org.apache.hop.core.gui.plugin.key.GuiOsxKeyboardShortcut;
 import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
 import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
 import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElementType;
-import org.apache.hop.core.logging.ILogChannel;
 import org.apache.hop.core.logging.ILoggingObject;
-import org.apache.hop.core.logging.LogChannel;
 import org.apache.hop.core.logging.LoggingObject;
 import org.apache.hop.core.row.IRowMeta;
 import org.apache.hop.core.search.SearchMatcher;
@@ -66,6 +63,8 @@ import org.apache.hop.ui.core.widget.HopTree;
 import org.apache.hop.ui.hopgui.HopGui;
 import org.apache.hop.ui.hopgui.HopGuiKeyHandler;
 import org.apache.hop.ui.hopgui.ToolbarFacade;
+import org.apache.hop.ui.hopgui.perspective.database.DatabaseWorkbenchDialog;
+import org.apache.hop.ui.hopgui.perspective.database.DatabaseWorkbenchViews;
 import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Image;
@@ -107,6 +106,8 @@ public class DatabaseExplorerDialog extends Dialog {
       "DatabaseExplorerDialog-Toolbar-10060-ShowLayout";
   public static final String TOOLBAR_ITEM_SQL_SELECT =
       "DatabaseExplorerDialog-Toolbar-10070-SqlSelect";
+  public static final String TOOLBAR_ITEM_OPEN_PERSPECTIVE =
+      "DatabaseExplorerDialog-Toolbar-20000-OpenPerspective";
 
   public static final String CONTEXT_MENU_PREVIEW_100 =
       "DatabaseExplorerDialog-ContextMenu-10010-Preview100";
@@ -141,10 +142,8 @@ public class DatabaseExplorerDialog extends Dialog {
   /** Debounced search action so we don't rebuild the tree on every keystroke. 
*/
   private final Runnable filterRunnable = this::updateTree;
 
-  private final ILogChannel log;
   private final DatabaseMeta databaseMeta;
   private final IVariables variables;
-  private final DbCache dbCache;
   private final ILoggingObject loggingObject;
   private final List<DatabaseMeta> databases;
   private final boolean justLook;
@@ -191,19 +190,11 @@ public class DatabaseExplorerDialog extends Dialog {
     filterUseRegEx = false;
     selectedSchemaName = null;
     selectedTableName = null;
-    log = new LogChannel("DBExplorer");
-    dbCache = DbCache.getInstance();
   }
 
   public boolean open() {
-    if (Const.isLinux()) {
-      shell =
-          new Shell(
-              getParent(),
-              SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | 
SWT.MIN);
-    } else {
-      shell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | 
SWT.MIN);
-    }
+    // Modeless so the floating Database window (SQL / DDL) can stay usable on 
Linux.
+    shell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MAX | 
SWT.MIN);
     PropsUi.setLook(shell);
     shell.setImage(GuiResource.getInstance().getImageDatabase());
     // Do not include the connection name in the title (to save/restore the 
dialog box dimensions
@@ -866,7 +857,7 @@ public class DatabaseExplorerDialog extends Dialog {
               new Database(loggingObject, variables, targetDatabaseMeta)) {
             String sql =
                 targetDatabase.getCreateTableStatement(table, rowMeta, null, 
false, null, true);
-            openSqlEditor(sql);
+            openSqlEditor(targetDatabaseMeta, sql);
           }
         }
       } catch (HopDatabaseException dbe) {
@@ -923,9 +914,24 @@ public class DatabaseExplorerDialog extends Dialog {
     }
   }
 
+  @GuiToolbarElement(
+      root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+      id = TOOLBAR_ITEM_OPEN_PERSPECTIVE,
+      type = GuiToolbarElementType.BUTTON,
+      toolTip = "i18n::DatabaseExplorerDialog.Toolbar.OpenPerspective.Tooltip",
+      image = "ui/images/database-perspective.svg",
+      separator = true)
+  public void openInDatabasePerspective() {
+    cancel();
+    DatabaseWorkbenchViews.openInDatabase(databaseMeta, "");
+  }
+
   protected void openSqlEditor(String sql) {
-    SqlEditor dialog = new SqlEditor(shell, SWT.NONE, variables, databaseMeta, 
dbCache, sql);
-    dialog.open();
+    openSqlEditor(databaseMeta, sql);
+  }
+
+  protected void openSqlEditor(DatabaseMeta meta, String sql) {
+    DatabaseWorkbenchDialog.openSql(meta, sql);
   }
 
   public void dispose() {
diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java 
b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
index faff4df3c5..0abc13c653 100644
--- a/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
+++ b/ui/src/main/java/org/apache/hop/ui/hopgui/HopGui.java
@@ -138,6 +138,7 @@ import 
org.apache.hop.ui.hopgui.perspective.HopPerspectivePlugin;
 import org.apache.hop.ui.hopgui.perspective.HopPerspectivePluginType;
 import org.apache.hop.ui.hopgui.perspective.IHopPerspective;
 import 
org.apache.hop.ui.hopgui.perspective.configuration.ConfigurationPerspective;
+import org.apache.hop.ui.hopgui.perspective.database.DatabasePerspective;
 import org.apache.hop.ui.hopgui.perspective.database.DatabaseSqlEditorTab;
 import org.apache.hop.ui.hopgui.perspective.execution.ExecutionPerspective;
 import org.apache.hop.ui.hopgui.perspective.explorer.ExplorerPerspective;
@@ -3011,6 +3012,10 @@ public class HopGui
     return 
HopGui.getInstance().getPerspectiveManager().findPerspective(MetadataPerspective.class);
   }
 
+  public static DatabasePerspective getDatabasePerspective() {
+    return 
HopGui.getInstance().getPerspectiveManager().findPerspective(DatabasePerspective.class);
+  }
+
   public static ExecutionPerspective getExecutionPerspective() {
     return 
HopGui.getInstance().getPerspectiveManager().findPerspective(ExecutionPerspective.class);
   }
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabasePerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabasePerspective.java
index 35f57e39f3..22087e618b 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabasePerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabasePerspective.java
@@ -18,6 +18,7 @@
 package org.apache.hop.ui.hopgui.perspective.database;
 
 import java.util.List;
+import org.apache.hop.core.Const;
 import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
 import org.apache.hop.core.gui.plugin.key.GuiKeyboardShortcut;
@@ -202,6 +203,18 @@ public class DatabasePerspective implements 
IHopPerspective, TabClosable, IDatab
     workbench.openSqlFile(filename, connection, buffer, dirty);
   }
 
+  /**
+   * Bring this perspective to the front, select {@code meta} in the tree, 
connect in the
+   * background, and open a SQL tab with {@code sql} (may be empty).
+   */
+  public void openConnection(DatabaseMeta meta, String sql) {
+    if (!isInitialized() || meta == null) {
+      return;
+    }
+    activate();
+    workbench.openSuggestedSql(meta, Const.NVL(sql, ""));
+  }
+
   public List<String> connectionNames() {
     if (!isInitialized()) {
       return List.of();
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 aa3c101b89..a0395af582 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
@@ -53,6 +53,7 @@ import org.apache.hop.ui.core.gui.GuiMenuWidgets;
 import org.apache.hop.ui.core.gui.GuiResource;
 import org.apache.hop.ui.core.gui.GuiToolbarWidgets;
 import org.apache.hop.ui.core.gui.IToolbarContainer;
+import org.apache.hop.ui.core.metadata.MetadataManager;
 import org.apache.hop.ui.core.widget.TreeMemory;
 import org.apache.hop.ui.hopgui.BackgroundThreadFacade;
 import org.apache.hop.ui.hopgui.HopGui;
@@ -64,6 +65,7 @@ import org.apache.hop.ui.hopgui.perspective.TabCloseHandler;
 import org.apache.hop.ui.hopgui.perspective.TabItemHandler;
 import 
org.apache.hop.ui.hopgui.perspective.database.config.DatabasePerspectiveConfig;
 import 
org.apache.hop.ui.hopgui.perspective.database.config.DatabasePerspectiveConfigSingleton;
+import org.apache.hop.ui.hopgui.perspective.metadata.MetadataPerspective;
 import org.apache.hop.ui.hopgui.shared.SashFormMemory;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.CTabFolder;
@@ -96,6 +98,8 @@ public class DatabaseWorkbench extends Composite implements 
TabClosable {
   public static final String TOOLBAR_ITEM_CONNECT = 
"DatabaseWorkbench-Toolbar-10000-Connect";
   public static final String TOOLBAR_ITEM_DISCONNECT = 
"DatabaseWorkbench-Toolbar-10010-Disconnect";
   public static final String TOOLBAR_ITEM_REFRESH = 
"DatabaseWorkbench-Toolbar-10020-Refresh";
+  public static final String TOOLBAR_ITEM_EDIT_METADATA =
+      "DatabaseWorkbench-Toolbar-10025-EditMetadata";
   public static final String TOOLBAR_ITEM_SQL = 
"DatabaseWorkbench-Toolbar-10030-SqlEditor";
   public static final String TOOLBAR_ITEM_DDL = 
"DatabaseWorkbench-Toolbar-10040-GenerateDdl";
   public static final String TOOLBAR_ITEM_PREVIEW = 
"DatabaseWorkbench-Toolbar-10050-Preview";
@@ -106,6 +110,8 @@ public class DatabaseWorkbench extends Composite implements 
TabClosable {
   public static final String CONTEXT_MENU_CONNECT = 
"DatabaseWorkbench-ContextMenu-10000-Connect";
   public static final String CONTEXT_MENU_DISCONNECT =
       "DatabaseWorkbench-ContextMenu-10010-Disconnect";
+  public static final String CONTEXT_MENU_EDIT_METADATA =
+      "DatabaseWorkbench-ContextMenu-10020-EditMetadata";
   public static final String CONTEXT_MENU_SQL = 
"DatabaseWorkbench-ContextMenu-10030-SqlEditor";
   public static final String CONTEXT_MENU_DDL = 
"DatabaseWorkbench-ContextMenu-10040-GenerateDdl";
   public static final String CONTEXT_MENU_PREVIEW = 
"DatabaseWorkbench-ContextMenu-10050-Preview";
@@ -776,6 +782,7 @@ public class DatabaseWorkbench extends Composite implements 
TabClosable {
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_CONNECT, hasConnection && 
!connected);
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_DISCONNECT, hasConnection && 
connected);
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_REFRESH, true);
+    toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_EDIT_METADATA, 
hasConnection);
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_SQL, hasConnection);
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_DDL, table);
     toolBarWidgets.enableToolbarItem(TOOLBAR_ITEM_PREVIEW, table);
@@ -785,6 +792,7 @@ public class DatabaseWorkbench extends Composite implements 
TabClosable {
 
     enableMenu(CONTEXT_MENU_CONNECT, hasConnection && !connected);
     enableMenu(CONTEXT_MENU_DISCONNECT, hasConnection && connected);
+    enableMenu(CONTEXT_MENU_EDIT_METADATA, hasConnection);
     enableMenu(CONTEXT_MENU_SQL, hasConnection);
     enableMenu(CONTEXT_MENU_DDL, table);
     enableMenu(CONTEXT_MENU_PREVIEW, table);
@@ -981,6 +989,45 @@ public class DatabaseWorkbench extends Composite 
implements TabClosable {
     reloadConnections();
   }
 
+  @GuiToolbarElement(
+      root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
+      id = TOOLBAR_ITEM_EDIT_METADATA,
+      toolTip = "i18n::DatabasePerspective.Toolbar.EditMetadata.Tooltip",
+      image = "ui/images/metadata.svg")
+  @GuiMenuElement(
+      root = GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
+      parentId = GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
+      id = CONTEXT_MENU_EDIT_METADATA,
+      label = "i18n::DatabasePerspective.Menu.EditMetadata",
+      image = "ui/images/metadata.svg")
+  public void editSelectedConnection() {
+    DatabaseConnectionState state = selectedState();
+    if (state == null || state.getDatabaseMeta() == null) {
+      return;
+    }
+    String name = state.getDatabaseMeta().getName();
+    if (Utils.isEmpty(name)) {
+      return;
+    }
+    HopGui hopGui = host.getHopGui();
+    if (hopGui == null) {
+      return;
+    }
+    MetadataManager<DatabaseMeta> manager =
+        new MetadataManager<>(
+            host.getVariables(), host.getMetadataProvider(), 
DatabaseMeta.class, host.getShell());
+    MetadataPerspective perspective =
+        hopGui.getPerspectiveManager() == null
+            ? null
+            : 
hopGui.getPerspectiveManager().findPerspective(MetadataPerspective.class);
+    if (perspective != null) {
+      perspective.activate();
+      manager.editWithEditor(name);
+    } else {
+      manager.editMetadataInDialog(name);
+    }
+  }
+
   @GuiToolbarElement(
       root = GUI_PLUGIN_TOOLBAR_PARENT_ID,
       id = TOOLBAR_ITEM_SQL,
@@ -1265,11 +1312,12 @@ public class DatabaseWorkbench extends Composite 
implements TabClosable {
     if (meta == null || connections == null) {
       return null;
     }
-    DatabaseMeta freshMeta =
-        reloadConnectionMeta(meta.getName(), host, connections, items, 
onTreeChanged);
-    if (freshMeta == null && host != null && host.getMetadataProvider() != 
null) {
+    if (Utils.isEmpty(meta.getName())) {
       return null;
     }
+    DatabaseMeta freshMeta =
+        reloadConnectionMeta(meta.getName(), host, connections, items, 
onTreeChanged);
+    // Not in the serializer: still use the caller-supplied definition 
(unsaved editor, explorer).
     DatabaseMeta effectiveMeta = freshMeta != null ? freshMeta : meta;
     DatabaseConnectionState state = connections.get(effectiveMeta.getName());
     if (state == null) {
diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViews.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViews.java
index 7c19ae0e14..0222ceb2fe 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViews.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViews.java
@@ -17,6 +17,7 @@
 
 package org.apache.hop.ui.hopgui.perspective.database;
 
+import org.apache.hop.core.database.DatabaseMeta;
 import org.apache.hop.core.gui.plugin.GuiPlugin;
 import org.apache.hop.core.gui.plugin.menu.GuiMenuElement;
 import org.apache.hop.i18n.BaseMessages;
@@ -66,6 +67,39 @@ public class DatabaseWorkbenchViews {
     DatabaseWorkbenchDialog.open(hopGui);
   }
 
+  /**
+   * Open {@code meta} in the Database perspective when that perspective is 
enabled, otherwise in
+   * the floating Database window. {@code sql} may be empty.
+   */
+  public static void openInDatabase(DatabaseMeta meta, String sql) {
+    HopGui hopGui;
+    try {
+      hopGui = HopGui.peekInstance();
+    } catch (Throwable e) {
+      hopGui = null;
+    }
+    openInDatabase(hopGui, meta, sql);
+  }
+
+  /**
+   * Open {@code meta} in the Database perspective when that perspective is 
enabled, otherwise in
+   * the floating Database window. {@code sql} may be empty.
+   */
+  public static void openInDatabase(HopGui hopGui, DatabaseMeta meta, String 
sql) {
+    if (meta == null) {
+      return;
+    }
+    if (hopGui != null && hopGui.getPerspectiveManager() != null) {
+      DatabasePerspective perspective =
+          
hopGui.getPerspectiveManager().findPerspective(DatabasePerspective.class);
+      if (perspective != null) {
+        perspective.openConnection(meta, sql);
+        return;
+      }
+    }
+    DatabaseWorkbenchDialog.openSql(meta, sql);
+  }
+
   /** True when the floating Database window is already open. */
   public static boolean isDialogOpen(HopGui hopGui) {
     if (hopGui == null || hopGui.getShell() == null || 
hopGui.getShell().isDisposed()) {
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/core/database/dialog/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/core/database/dialog/messages/messages_en_US.properties
index 72bbf3d143..6a88d76058 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/core/database/dialog/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/core/database/dialog/messages/messages_en_US.properties
@@ -27,6 +27,7 @@ DatabaseExplorerDialog.Menu.ShowLayout=Show layout...
 DatabaseExplorerDialog.Menu.ShowSize=Number of rows...
 DatabaseExplorerDialog.Menu.Truncate=Truncate table...
 DatabaseExplorerDialog.Toolbar.SQLSelect.Tooltip=Generate SQL Select...
+DatabaseExplorerDialog.Toolbar.OpenPerspective.Tooltip=Open this connection in 
the Database perspective
 DatabaseExplorerDialog.Toolbar.Preview.Tooltip=Preview first 100 rows...
 DatabaseExplorerDialog.Toolbar.ShowLayout.Tooltip=Show layout...
 DatabaseExplorerDialog.NoConnectionsKnown.Message=At this point no other 
connections are available to generate the DDL for.
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/core/database/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/core/database/messages/messages_en_US.properties
index 41d216a9f5..6ae91017b7 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/core/database/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/core/database/messages/messages_en_US.properties
@@ -18,6 +18,10 @@
 DatabaseDialog.AdvancedTab.title=Advanced
 DatabaseDialog.button.DownloadDriver=Download driver
 DatabaseDialog.button.Explore=Explore
+DatabaseDialog.button.OpenInDatabase=Open in Database
+DatabaseDialog.button.OpenInDatabase.Tooltip=Open this connection in the 
Database perspective to explore and query
+DatabaseDialog.OpenInDatabase.NameRequired.Title=Connection name required
+DatabaseDialog.OpenInDatabase.NameRequired.Message=Please enter a connection 
name before opening the Database perspective.
 DatabaseDialog.button.GenerateVariables=Generate variables
 DatabaseDialog.GenerateVariables.NameRequired.Title=Connection name required
 DatabaseDialog.GenerateVariables.NameRequired.Message=Please enter a 
connection name before generating variables. The name is used as a prefix (for 
example EDW_HOSTNAME).
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 cb8c4fbfab..21ccda47e7 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
@@ -28,6 +28,7 @@ DatabasePerspective.Tree.Synonyms=Synonyms
 DatabasePerspective.Toolbar.Connect.Tooltip=Connect and list schemas and tables
 DatabasePerspective.Toolbar.Disconnect.Tooltip=Forget loaded schemas and 
tables for this connection
 DatabasePerspective.Toolbar.Refresh.Tooltip=Reload the selected connection, or 
the connection list
+DatabasePerspective.Toolbar.EditMetadata.Tooltip=Edit this connection in the 
Metadata perspective
 DatabasePerspective.Toolbar.SqlEditor.Tooltip=Open a new SQL editor for the 
selected connection
 DatabasePerspective.Toolbar.GenerateDdl.Tooltip=Generate CREATE TABLE for the 
selected table
 DatabasePerspective.Toolbar.Preview.Tooltip=Preview the selected table using 
the query row limit from the Database perspective options
@@ -36,6 +37,7 @@ DatabasePerspective.Toolbar.Float.Tooltip=Open a floating 
Database window
 DatabasePerspective.Toolbar.Dock.Tooltip=Open Database in the bottom dock
 DatabasePerspective.Menu.Connect=Connect
 DatabasePerspective.Menu.Disconnect=Disconnect
+DatabasePerspective.Menu.EditMetadata=Edit connection
 DatabasePerspective.Menu.SqlEditor=Open SQL editor
 DatabasePerspective.Menu.GenerateDdl=Generate DDL
 DatabasePerspective.Menu.Preview=Preview table
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/DisabledPerspectivesTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/DisabledPerspectivesTest.java
index ea3dbbf6fd..33ba539efb 100644
--- 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/DisabledPerspectivesTest.java
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/DisabledPerspectivesTest.java
@@ -88,6 +88,7 @@ class DisabledPerspectivesTest {
           perspective.activate();
           perspective.clearSearchFilters();
           perspective.openSqlFile("x.sql", null, "select 1", false);
+          perspective.openConnection(null, "select 1");
         });
     assertTrue(perspective.getItems().isEmpty());
     assertTrue(perspective.connectionNames().isEmpty());
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
index 71bc2eb7c5..5bca83a252 100644
--- 
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
@@ -118,19 +118,31 @@ class DatabaseWorkbenchReloadMetaTest {
   }
 
   @Test
-  void ensureConnectionReturnsNullWhenDeleted() throws Exception {
+  void ensureConnectionFallsBackToSuppliedMetaWhenNotInSerializer() 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(meta.getName()).thenReturn("unsaved-conn");
 
-    when(serializer.exists("deleted-conn")).thenReturn(false);
+    when(serializer.exists("unsaved-conn")).thenReturn(false);
 
     DatabaseConnectionState state =
         DatabaseWorkbench.ensureConnection(meta, host, connections, List.of(), 
null);
 
-    assertNull(state);
-    assertFalse(connections.containsKey("deleted-conn"));
+    assertNotNull(state);
+    assertSame(meta, state.getDatabaseMeta());
+    assertTrue(connections.containsKey("unsaved-conn"));
+  }
+
+  @Test
+  void ensureConnectionReturnsNullForBlankName() {
+    Map<String, DatabaseConnectionState> connections = new LinkedHashMap<>();
+    DatabaseMeta emptyName = mock(DatabaseMeta.class);
+    when(emptyName.getName()).thenReturn("");
+    assertNull(DatabaseWorkbench.ensureConnection(emptyName, host, 
connections, List.of(), null));
+
+    DatabaseMeta unnamed = mock(DatabaseMeta.class);
+    when(unnamed.getName()).thenReturn(null);
+    assertNull(DatabaseWorkbench.ensureConnection(unnamed, host, connections, 
List.of(), null));
   }
 
   @Test
diff --git 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViewsTest.java
 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViewsTest.java
index 9b857ba218..98e1c28d4e 100644
--- 
a/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViewsTest.java
+++ 
b/ui/src/test/java/org/apache/hop/ui/hopgui/perspective/database/DatabaseWorkbenchViewsTest.java
@@ -18,8 +18,13 @@
 package org.apache.hop.ui.hopgui.perspective.database;
 
 import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+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 java.lang.reflect.Method;
+import org.apache.hop.core.gui.plugin.toolbar.GuiToolbarElement;
+import org.apache.hop.ui.core.database.dialog.DatabaseExplorerDialog;
 import org.junit.jupiter.api.Test;
 
 class DatabaseWorkbenchViewsTest {
@@ -49,4 +54,30 @@ class DatabaseWorkbenchViewsTest {
   void openSqlIsInertWithoutHopGui() {
     assertDoesNotThrow(() -> DatabaseWorkbenchDialog.openSql(null, "SELECT 
1"));
   }
+
+  @Test
+  void openInDatabaseIsInertWithoutHopGui() {
+    assertDoesNotThrow(() -> DatabaseWorkbenchViews.openInDatabase(null, null, 
"SELECT 1"));
+    assertDoesNotThrow(() -> DatabaseWorkbenchViews.openInDatabase(null, 
"SELECT 1"));
+  }
+
+  @Test
+  void editMetadataToolbarIsRegisteredOnTheWorkbench() throws Exception {
+    Method method = 
DatabaseWorkbench.class.getMethod("editSelectedConnection");
+    GuiToolbarElement element = method.getAnnotation(GuiToolbarElement.class);
+    assertNotNull(element);
+    assertEquals(DatabaseWorkbench.GUI_PLUGIN_TOOLBAR_PARENT_ID, 
element.root());
+    assertEquals(DatabaseWorkbench.TOOLBAR_ITEM_EDIT_METADATA, element.id());
+    assertEquals("ui/images/metadata.svg", element.image());
+  }
+
+  @Test
+  void openPerspectiveToolbarIsRegisteredOnTheExplorer() throws Exception {
+    Method method = 
DatabaseExplorerDialog.class.getMethod("openInDatabasePerspective");
+    GuiToolbarElement element = method.getAnnotation(GuiToolbarElement.class);
+    assertNotNull(element);
+    assertEquals(DatabaseExplorerDialog.GUI_PLUGIN_TOOLBAR_PARENT_ID, 
element.root());
+    assertEquals(DatabaseExplorerDialog.TOOLBAR_ITEM_OPEN_PERSPECTIVE, 
element.id());
+    assertEquals("ui/images/database-perspective.svg", element.image());
+  }
 }

Reply via email to