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) }}

-