From a81d6da6048a0018bdef033b3feefe3849bc9bc9 Mon Sep 17 00:00:00 2001
From: zipg
Date: Wed, 22 Jul 2026 17:40:40 +0800
Subject: [PATCH] fix(dameng): support configurable system schemas
---
.../com/dbx/agent/dameng/DamengAgent.java | 19 +-------
.../agent/dameng/DamengAgentMetadataTest.java | 36 +++++++++++++--
.../connection/ConnectionDialog.vue | 41 +++++++++--------
.../sidebar/VisibleDatabasesDialog.vue | 22 +++++----
.../sidebar/VisibleSchemasDialog.vue | 45 ++++++++++++++-----
apps/desktop/src/i18n/locales/en.ts | 1 +
apps/desktop/src/i18n/locales/es.ts | 1 +
apps/desktop/src/i18n/locales/it.ts | 1 +
apps/desktop/src/i18n/locales/ja.ts | 1 +
apps/desktop/src/i18n/locales/pt-BR.ts | 1 +
apps/desktop/src/i18n/locales/zh-CN.ts | 1 +
apps/desktop/src/i18n/locales/zh-TW.ts | 1 +
.../connection/connectionVisibleDatabases.ts | 8 ++++
.../src/lib/database/visibleDatabases.ts | 11 +++--
.../connectionVisibleDatabases.test.ts | 23 ++++++++--
15 files changed, 146 insertions(+), 66 deletions(-)
diff --git a/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java b/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java
index ca81d1716..6588f6bf1 100644
--- a/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java
+++ b/agents/drivers/dameng/src/main/java/com/dbx/agent/dameng/DamengAgent.java
@@ -72,11 +72,6 @@ public final class DamengAgent extends BaseDatabaseAgent {
ON schema_object.OBJECT_ID = m.SCHID AND schema_object.OBJECT_TYPE = 'SCH'
) mv ON mv.OWNER = o.OWNER AND mv.MVIEW_NAME = o.OBJECT_NAME
""".stripIndent().trim();
- private static final Set SYSTEM_USERS = Set.of(
- "SYS", "SYSAUDITOR", "SYSSSO", "CTISYS",
- "SYSDBA", "SYS_DBA", "_SYS_STATISTICS", "SYS_PHM"
- );
-
private Connection connection;
private String connectedUsername;
@@ -157,13 +152,8 @@ public final class DamengAgent extends BaseDatabaseAgent {
private List listVisibleUsers() throws Exception {
List result = new ArrayList<>();
- String placeholders = String.join(",", SYSTEM_USERS.stream().map(user -> "?").toList());
- String sql = "SELECT USERNAME FROM ALL_USERS WHERE USERNAME NOT IN (" + placeholders + ") ORDER BY USERNAME";
+ String sql = "SELECT USERNAME FROM ALL_USERS ORDER BY USERNAME";
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
- int index = 1;
- for (String user : SYSTEM_USERS) {
- stmt.setString(index++, user);
- }
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
result.add(rs.getString(1));
@@ -175,13 +165,8 @@ public final class DamengAgent extends BaseDatabaseAgent {
private List listVisibleSchemas() throws Exception {
List result = new ArrayList<>();
- String placeholders = String.join(",", SYSTEM_USERS.stream().map(user -> "?").toList());
- String sql = "SELECT NAME FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' AND NAME NOT IN (" + placeholders + ") ORDER BY NAME";
+ String sql = "SELECT NAME FROM SYS.SYSOBJECTS WHERE TYPE$ = 'SCH' ORDER BY NAME";
try (PreparedStatement stmt = requireConnected().prepareStatement(sql)) {
- int index = 1;
- for (String user : SYSTEM_USERS) {
- stmt.setString(index++, user);
- }
try (ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
result.add(rs.getString(1));
diff --git a/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java b/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java
index 09b5e28f9..a901d5c23 100644
--- a/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java
+++ b/agents/drivers/dameng/src/test/java/com/dbx/agent/dameng/DamengAgentMetadataTest.java
@@ -106,12 +106,24 @@ class DamengAgentMetadataTest {
List schemas = agent.listSchemas();
- Assertions.assertEquals(List.of("APP", "EMPTY_SCHEMA"), schemas);
+ Assertions.assertEquals(List.of("APP", "EMPTY_SCHEMA", "SYSDBA"), schemas);
Assertions.assertTrue(sqls.stream().anyMatch(sql -> sql.contains("SYS.SYSOBJECTS") && sql.contains("TYPE$ = 'SCH'")), String.join("\n", sqls));
Assertions.assertTrue(sqls.stream().noneMatch(sql -> sql.contains("ALL_USERS")), String.join("\n", sqls));
Assertions.assertTrue(sqls.stream().noneMatch(sql -> sql.contains("ALL_OBJECTS")), String.join("\n", sqls));
}
+ @Test
+ void listSchemasLeavesSysdbaAvailableForFrontendFiltering() {
+ DamengAgent agent = new DamengAgent();
+ List params = new ArrayList<>();
+ TestSupport.setPrivateConnection(agent, schemaConnection(params));
+
+ List schemas = agent.listSchemas();
+
+ Assertions.assertEquals(List.of("APP", "SYSDBA"), schemas);
+ Assertions.assertTrue(params.isEmpty(), params.toString());
+ }
+
@Test
void listSchemasFallsBackToAllUsersWithoutSysObjectsPrivilege() {
DamengAgent agent = new DamengAgent();
@@ -120,7 +132,7 @@ class DamengAgentMetadataTest {
List schemas = agent.listSchemas();
- Assertions.assertEquals(List.of("APP", "REPORTING"), schemas);
+ Assertions.assertEquals(List.of("APP", "REPORTING", "SYSDBA"), schemas);
Assertions.assertEquals(2, sqls.size(), String.join("\n", sqls));
Assertions.assertTrue(sqls.get(0).contains("SYS.SYSOBJECTS"), sqls.get(0));
Assertions.assertTrue(sqls.get(1).contains("ALL_USERS"), sqls.get(1));
@@ -522,7 +534,7 @@ class DamengAgentMetadataTest {
return dbmsMetadataStatement(dbmsMetadataDdl, dbmsMetadataResultOpen);
}
if (sql.contains("SYS.SYSOBJECTS") && sql.contains("TYPE$ = 'SCH'")) {
- return metadataStatement(List.of(List.of("APP"), List.of("EMPTY_SCHEMA")));
+ return metadataStatement(List.of(List.of("APP"), List.of("EMPTY_SCHEMA"), List.of("SYSDBA")));
}
if (sql.contains("ALL_CONS_COLUMNS")) {
return metadataStatement(List.of(List.of("ID")));
@@ -720,7 +732,7 @@ class DamengAgentMetadataTest {
return failingMetadataStatement("no SYS.SYSOBJECTS privilege");
}
if (sql.contains("ALL_USERS")) {
- return metadataStatement(List.of(List.of("APP"), List.of("REPORTING")));
+ return metadataStatement(List.of(List.of("APP"), List.of("REPORTING"), List.of("SYSDBA")));
}
throw new AssertionError("Unexpected SQL: " + sql);
}
@@ -734,6 +746,22 @@ class DamengAgentMetadataTest {
});
}
+ private static Connection schemaConnection(List params) {
+ return proxy(Connection.class, (method, args) -> {
+ String name = method.getName();
+ if ("prepareStatement".equals(name)) {
+ return metadataStatement(List.of(List.of("APP"), List.of("SYSDBA")), params);
+ }
+ if ("close".equals(name)) {
+ return null;
+ }
+ if ("isClosed".equals(name)) {
+ return false;
+ }
+ return defaultValue(method.getReturnType());
+ });
+ }
+
private static PreparedStatement failingMetadataStatement(String message) {
return proxy(PreparedStatement.class, (method, args) -> {
if ("executeQuery".equals(method.getName())) {
diff --git a/apps/desktop/src/components/connection/ConnectionDialog.vue b/apps/desktop/src/components/connection/ConnectionDialog.vue
index 0432f8aae..1715ce893 100644
--- a/apps/desktop/src/components/connection/ConnectionDialog.vue
+++ b/apps/desktop/src/components/connection/ConnectionDialog.vue
@@ -80,8 +80,8 @@ import {
Square,
Trash2,
} from "@lucide/vue";
-import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleDatabaseSelectionIsStale } from "@/lib/connection/connectionVisibleDatabases";
-import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, isSystemDatabaseName, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
+import { buildDraftVisibleDatabasesConnectionId, connectionCanChooseVisibleDatabases, initialVisibleDatabaseSelection, visibleObjectFiltersNeedReset } from "@/lib/connection/connectionVisibleDatabases";
+import { canSaveVisibleDatabaseSelection, connectionUsesVisibleSchemaFilter, filterDatabaseNamesForVisiblePicker, filterSchemaNamesForVisiblePicker, normalizeVisibleDatabaseSelection, buildDraftVisibleSchemasConnectionId, normalizeVisibleSchemaSelection } from "@/lib/database/visibleDatabases";
import { isSchemaAware, isSingleDatabase } from "@/lib/database/databaseFeatureSupport";
import VisibleSchemasDialog from "@/components/sidebar/VisibleSchemasDialog.vue";
import CloudflareD1ConnectionFields from "@/components/connection/CloudflareD1ConnectionFields.vue";
@@ -1745,6 +1745,7 @@ watch(
is_production: config.is_production || false,
production_databases: config.production_databases || [],
visible_databases: config.visible_databases,
+ visible_schemas: config.visible_schemas,
};
productionProtectionEnabled.value = !!config.is_production || (config.production_databases?.length ?? 0) > 0;
connectionUrlInput.value = config.db_type === "h2" && config.connection_string ? config.connection_string : "";
@@ -2240,12 +2241,12 @@ const visibleDatabaseSummary = computed(() => {
if (!Array.isArray(configured)) return t("visibleDatabases.showAll");
return t("visibleDatabases.selectedCount", { selected: configured.length, total: visibleDatabaseNames.value.length });
});
-const listedVisibleDatabaseNames = computed(() => {
- if (visibleFilterUsesSchemas.value) return visibleDatabaseNames.value;
+const defaultListedVisibleDatabaseNames = computed(() => {
const connection = connectionConfigSnapshotForVisibleDatabases();
- if (visibleDatabaseShowSystem.value) return visibleDatabaseNames.value;
+ if (visibleFilterUsesSchemas.value) return filterSchemaNamesForVisiblePicker(visibleDatabaseNames.value, connection);
return filterDatabaseNamesForVisiblePicker(visibleDatabaseNames.value, connection);
});
+const listedVisibleDatabaseNames = computed(() => (visibleDatabaseShowSystem.value ? visibleDatabaseNames.value : defaultListedVisibleDatabaseNames.value));
const filteredVisibleDatabaseNames = computed(() => {
const query = visibleDatabaseSearchText.value.trim().toLowerCase();
if (!query) return listedVisibleDatabaseNames.value;
@@ -2254,11 +2255,8 @@ const filteredVisibleDatabaseNames = computed(() => {
const visibleDatabaseSelectedCount = computed(() => visibleDatabaseSelection.value.size);
const visibleDatabaseTotalCount = computed(() => listedVisibleDatabaseNames.value.length);
const visibleDatabaseCanSave = computed(() => canSaveVisibleDatabaseSelection([...visibleDatabaseSelection.value]));
-const visibleDatabaseHasSystemDatabases = computed(() => {
- if (visibleFilterUsesSchemas.value) return false;
- const connection = connectionConfigSnapshotForVisibleDatabases();
- return visibleDatabaseNames.value.some((database) => isSystemDatabaseName(connection.db_type, database));
-});
+const visibleDatabaseHasSystemObjects = computed(() => defaultListedVisibleDatabaseNames.value.length < visibleDatabaseNames.value.length);
+const visibleSystemObjectsLabelKey = computed(() => (visibleFilterUsesSchemas.value ? "visibleSchemas.showSystemSchemas" : "visibleDatabases.showSystemDatabases"));
const filteredProductionDatabaseNames = computed(() => {
const query = productionDatabaseSearchText.value.trim().toLowerCase();
if (!query) return productionDatabaseNames.value;
@@ -3227,10 +3225,12 @@ async function openVisibleDatabasesPicker() {
await api.connectDb(draftConfig);
const names = await loadVisibleDatabaseNames(draftId, draftConfig);
visibleDatabaseNames.value = names;
+ visibleDatabaseShowSystem.value = false;
const configuredSchemas = visibleSchemaObjectSelection.value;
- const initialSelection = visibleFilterUsesSchemas.value ? (Array.isArray(configuredSchemas) ? normalizeVisibleSchemaSelection(configuredSchemas, names) : names) : initialVisibleDatabaseSelection(names, form.value.visible_databases, draftConfig);
+ const initialSelection = visibleFilterUsesSchemas.value ? (Array.isArray(configuredSchemas) ? normalizeVisibleSchemaSelection(configuredSchemas, names) : filterSchemaNamesForVisiblePicker(names, draftConfig)) : initialVisibleDatabaseSelection(names, form.value.visible_databases, draftConfig);
visibleDatabaseSelection.value = new Set(initialSelection);
- visibleDatabaseShowSystem.value = !visibleFilterUsesSchemas.value && initialSelection.some((database) => isSystemDatabaseName(draftConfig.db_type, database));
+ const defaultVisible = new Set(defaultListedVisibleDatabaseNames.value);
+ visibleDatabaseShowSystem.value = initialSelection.some((name) => !defaultVisible.has(name));
showVisibleDatabasesDialog.value = true;
} catch (e: any) {
visibleDatabaseNames.value = [];
@@ -3623,18 +3623,19 @@ watch([() => form.value.db_type, () => form.value.username], () => {
watch(
() => connectionConfigSnapshotForVisibleDatabases(),
(current, previous) => {
- if (!previous || !form.value.visible_databases?.length) return;
- if (!visibleDatabaseSelectionIsStale(previous, current)) return;
+ if (!previous || !visibleObjectFiltersNeedReset(previous, current)) return;
form.value.visible_databases = undefined;
- visibleDatabaseNames.value = [];
- visibleDatabaseSelection.value = new Set();
+ form.value.visible_schemas = undefined;
+ resetVisibleDatabaseDraftState();
+ resetVisibleSchemasState();
},
);
watch(visibleDatabaseShowSystem, (show) => {
if (show) return;
const connection = connectionConfigSnapshotForVisibleDatabases();
- visibleDatabaseSelection.value = new Set([...visibleDatabaseSelection.value].filter((database) => !isSystemDatabaseName(connection.db_type, database)));
+ const visible = new Set(visibleFilterUsesSchemas.value ? filterSchemaNamesForVisiblePicker(visibleDatabaseNames.value, connection) : filterDatabaseNamesForVisiblePicker(visibleDatabaseNames.value, connection));
+ visibleDatabaseSelection.value = new Set([...visibleDatabaseSelection.value].filter((name) => visible.has(name)));
});
watch(canUseTransportLayers, (value) => {
@@ -6250,9 +6251,9 @@ function openExternalUrl(url: string) {
{{ t(visibleObjectEmptySelectionKey) }}
-