feat(metadata): honor system schema visibility

This commit is contained in:
amwps290 authored and GitHub committed 2026-07-29 03:23:04 +08:00
1 parent a07def6042
commit 820b8cd26f
44 files changed
+422 -47

No files matched your search

@@ -255,6 +255,7 @@ const defaultForm = (): ConnectionForm => ({
external_config: undefined,
init_script: undefined,
read_only: false,
show_system_schemas: false,
is_production: false,
production_databases: [],
visible_databases: undefined,
@@ -1994,6 +1995,7 @@ watch(
attached_databases: config.attached_databases || [],
init_script: config.init_script,
read_only: config.read_only || false,
show_system_schemas: config.show_system_schemas || false,
is_production: config.is_production || false,
production_databases: config.production_databases || [],
visible_databases: config.visible_databases,
@@ -3391,6 +3393,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
} else {
config.visible_databases = Array.isArray(config.visible_databases) && config.visible_databases.length > 0 ? config.visible_databases : undefined;
}
if (!config.show_system_schemas) config.show_system_schemas = undefined;
if (config.visible_schemas && Object.keys(config.visible_schemas).length === 0) config.visible_schemas = undefined;
if (config.agent_java_options && config.agent_java_options.length === 0) config.agent_java_options = undefined;
return config as ConnectionConfig;
@@ -6557,6 +6560,13 @@ function openExternalUrl(url: string) {
<span class="text-xs text-muted-foreground">{{ t("connection.readOnlyHint") }}</span>
</label>
</div>
<div v-if="isSchemaAware(form.db_type)" class="grid grid-cols-4 items-center gap-4">
<Label :class="connectionLabelSmallClass">{{ t("connection.showSystemSchemas") }}</Label>
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
<input type="checkbox" v-model="form.show_system_schemas" class="mr-0" />
<span class="text-xs text-muted-foreground">{{ t("connection.showSystemSchemasHint") }}</span>
</label>
</div>
<div class="grid grid-cols-4 items-start gap-4 rounded-[6px] border border-red-500/25 bg-red-500/[0.035] px-3 py-2.5">
<Label :class="[connectionLabelSmallClass, 'pt-0.5 text-red-700 dark:text-red-300']">
<span class="inline-flex items-center justify-end gap-1"><ShieldAlert class="h-3.5 w-3.5" />PROD</span>
@@ -95,6 +95,7 @@ import { formatShortcut } from "@/lib/editor/shortcutRegistry";
import { batchTableEmptyFeedback, buildBatchTableEmptyPlan, runBatchTableEmpty, type BatchTableEmptyPlanItem } from "@/lib/sidebar/batchTableEmpty";
import { runBatchTableDrop } from "@/lib/table/batchTableDrop";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { filterSchemaNamesForConnection } from "@/lib/database/visibleDatabases";
import {
buildObjectBrowserRows,
countObjectBrowserRowsByFilter,
@@ -415,6 +416,13 @@ watch(objectFilter, () => {
}
scrollObjectsToTop();
});
watch(
() => props.connection.show_system_schemas,
(value, oldValue) => {
if (value === oldValue) return;
void reload();
},
);
const showCheckboxColumn = computed(() => settingsStore.editorSettings.objectBrowserShowCheckbox || selectedTableCount.value > 0);
@@ -2186,9 +2194,15 @@ async function loadSchemas(epoch: number): Promise<boolean> {
const connectionId = props.connection.id;
const database = props.database;
try {
const names = await api.listSchemas(connectionId, database);
const names = filterSchemaNamesForConnection(await api.listSchemas(connectionId, database), props.connection, database, {
showSystemSchemas: props.connection.show_system_schemas === true,
});
if (!objectBrowserRowsLoadGuard.isEpochCurrent(epoch)) return false;
schemas.value = names;
if (names.length === 0) {
selectedSchema.value = undefined;
return true;
}
if (!selectedSchema.value || !names.includes(selectedSchema.value)) {
selectedSchema.value = names.includes("public") ? "public" : names[0];
}
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { schemaOptionsForConnection } from "@/composables/useSchemaOptions";
import { schemaOptionsCacheKey, schemaOptionsForConnection } from "@/composables/useSchemaOptions";
describe("schemaOptionsForConnection", () => {
it("sorts schema names with numeric suffixes naturally", () => {
@@ -19,4 +19,19 @@ describe("schemaOptionsForConnection", () => {
expect(schemaOptionsForConnection(["WMWMSE10", "SYSTEM", "WMWMSE2", "WMWMSE1"], connection, "ORCL")).toEqual(["WMWMSE2", "WMWMSE10"]);
});
it.each(["opengauss", "kingbase"] as const)("includes system schemas for %s when enabled", (dbType) => {
expect(
schemaOptionsForConnection(["information_schema", "pg_catalog", "public"], {
db_type: dbType,
show_system_schemas: true,
}),
).toEqual(["information_schema", "pg_catalog", "public"]);
});
});
describe("schemaOptionsCacheKey", () => {
it("separates hidden and visible system-schema results", () => {
expect(schemaOptionsCacheKey("connection-1", "postgres", false)).not.toBe(schemaOptionsCacheKey("connection-1", "postgres", true));
});
});
@@ -10,7 +10,11 @@ export function hasSchemaOptionsCacheEntry(options: Record<string, string[]>, ke
return Object.prototype.hasOwnProperty.call(options, key);
}
export function schemaOptionsForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases" | "visible_schemas"> | undefined, database = ""): string[] {
export function schemaOptionsCacheKey(connectionId: string, database: string, showSystemSchemas: boolean): string {
return `${connectionId}:${database}:${showSystemSchemas ? "show-system" : "hide-system"}`;
}
export function schemaOptionsForConnection(schemaNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases" | "visible_schemas" | "show_system_schemas"> | undefined, database = ""): string[] {
// Keep numeric schema suffixes in human order (SCHEMA2 before SCHEMA10), matching the database tree.
return sortSidebarNames(filterSchemaNamesForConnection(schemaNames, connection, database));
}
@@ -22,7 +26,7 @@ export function useSchemaOptions() {
const loadingSchemaOptions = ref<Record<string, boolean>>({});
function cacheKey(connectionId: string, database: string) {
return `${connectionId}:${database}`;
return schemaOptionsCacheKey(connectionId, database, connectionStore.getConfig(connectionId)?.show_system_schemas === true);
}
function isSchemaAware(connectionId: string): boolean {
+2
View File
@@ -627,6 +627,8 @@ export default {
keepaliveInterval: "Keepalive Interval (seconds)",
readOnly: "Read Only",
readOnlyHint: "Block all write operations (INSERT, UPDATE, DELETE, etc.)",
showSystemSchemas: "Show System Schemas",
showSystemSchemasHint: "Show built-in and metadata schemas in the sidebar and schema pickers for this connection.",
readOnlyBadge: "Read-only",
proxy: "Proxy",
proxyEnable: "Connect database through proxy",
+2
View File
@@ -659,6 +659,8 @@ export default withEnglishFallback({
rocketmqSecretKey: "Secret Key",
rocketmqAclAuth: "ACL",
jdbcMissingRuntimeDependencyHint: "El controlador JDBC actual carece de dependencias de ejecución. Utilice las coordenadas Maven en 'Administración de controladores' para instalar, o importe el controlador y todos los JAR de dependencia de una vez.",
showSystemSchemas: "Mostrar Schema del sistema",
showSystemSchemasHint: "Mostrar el Schema integrado/de metadatos para la conexión actual en la barra lateral y el selector de Schema.",
sshHostKeyVerifyTitle: "Confirmar clave de host SSH desconocida",
sshHostKeyVerifyMessage: "No se puede confirmar la autenticidad del host '{host}:{port}'. Para evitar ataques de intermediario, verifique la huella digital de la clave del host con el administrador del servidor antes de continuar.",
sshHostKeyVerifyKeyType: "Tipo de clave",
+2
View File
@@ -657,6 +657,8 @@ export default withEnglishFallback({
rocketmqSecretKey: "Secret Key",
rocketmqAclAuth: "ACL",
jdbcMissingRuntimeDependencyHint: "Il driver JDBC corrente manca di dipendenze runtime. Installare utilizzando le coordinate Maven in 'Gestione driver', o importare il driver e tutti i JAR delle dipendenze in una volta.",
showSystemSchemas: "Mostra Schema di sistema",
showSystemSchemasHint: "Mostra lo Schema built-in/di metadati nella barra laterale e nel selettore Schema per la connessione corrente.",
sshHostKeyVerifyTitle: "Conferma chiave host SSH sconosciuta",
sshHostKeyVerifyMessage: "Impossibile confermare l'autenticità dell'host '{host}:{port}'. Per prevenire attacchi man-in-the-middle, verifica l'impronta della chiave host con l'amministratore del server prima di procedere.",
sshHostKeyVerifyKeyType: "Tipo di chiave",
+2
View File
@@ -657,6 +657,8 @@ export default withEnglishFallback({
rocketmqSecretKey: "Secret Key",
rocketmqAclAuth: "ACL",
jdbcMissingRuntimeDependencyHint: "現在のJDBCドライバーには実行依存関係が不足しています。「ドライバ管理」でMaven座標を使用してインストールするか、ドライバーとすべての依存JARを一度にインポートしてください。",
showSystemSchemas: "システムスキーマを表示",
showSystemSchemasHint: "現在の接続で、サイドバーとスキーマセレクターに組み込み/メタデータスキーマを表示します。",
sshHostKeyVerifyTitle: "不明なSSHホストキーの確認",
sshHostKeyVerifyMessage: "ホスト '{host}:{port}' の正当性を確認できません。中間者攻撃を防ぐため、続行する前にサーバー管理者にホストキーのフィンガープリントを確認してください。",
sshHostKeyVerifyKeyType: "キータイプ",
+2
View File
@@ -658,6 +658,8 @@ export default withEnglishFallback({
rocketmqSecretKey: "Secret Key",
rocketmqAclAuth: "ACL",
jdbcMissingRuntimeDependencyHint: "O driver JDBC atual está sem dependências de execução. Por favor, instale-o usando as coordenadas Maven no 'Gerenciamento de Drivers' ou importe o driver e todos os JARs de dependência de uma só vez.",
showSystemSchemas: "Mostrar Schema do sistema",
showSystemSchemasHint: "Exibir Schemas de sistema/metadados integrados na barra lateral e no seletor de Schema para a conexão atual.",
sshHostKeyVerifyTitle: "Confirmar chave de host SSH desconhecida",
sshHostKeyVerifyMessage: "Não foi possível confirmar a autenticidade do host '{host}:{port}'. Para evitar ataques man-in-the-middle, verifique a impressão digital da chave do host com o administrador do servidor antes de continuar.",
sshHostKeyVerifyKeyType: "Tipo de chave",
+2
View File
@@ -630,6 +630,8 @@ export default withEnglishFallback({
keepaliveInterval: "保持连接间隔(秒)",
readOnly: "只读模式",
readOnlyHint: "阻止所有写操作(INSERT、UPDATE、DELETE 等)",
showSystemSchemas: "显示系统 Schema",
showSystemSchemasHint: "为当前连接在侧边栏和 Schema 选择器中显示内置/元数据 Schema。",
readOnlyBadge: "只读",
proxy: "代理",
proxyEnable: "通过代理连接数据库",
+2
View File
@@ -657,6 +657,8 @@ export default withEnglishFallback({
rocketmqSecretKey: "Secret Key",
rocketmqAclAuth: "ACL",
jdbcMissingRuntimeDependencyHint: "目前 JDBC 驅動缺少執行依賴。請在「驅動管理」中使用 Maven 座標安裝,或一次匯入驅動及全部依賴 JAR。",
showSystemSchemas: "顯示系統 Schema",
showSystemSchemasHint: "為目前連線在側邊欄和 Schema 選擇器中顯示內建/中繼資料 Schema。",
sshHostKeyVerifyTitle: "確認未知的 SSH 主機金鑰",
sshHostKeyVerifyMessage: "無法確認主機 '{host}:{port}' 的真實性。為防止中間人攻擊,請在繼續前與伺服器管理員核對主機金鑰指紋。",
sshHostKeyVerifyKeyType: "金鑰類型",
@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { filterSchemaNamesForConnection, filterSchemaNamesForVisiblePicker, isSystemSchemaName } from "@/lib/database/visibleDatabases";
describe("visibleDatabases schema filtering", () => {
it("hides common Kingbase system schemas by default", () => {
expect(filterSchemaNamesForVisiblePicker(["anon", "dbms_job", "information_schema", "pg_catalog", "public", "sys", "sys_catalog", "wmsys", "xlog_record_read"], { db_type: "kingbase", username: "test" })).toEqual(["public"]);
});
it("keeps the current schema visible even when it matches a system schema name", () => {
expect(
filterSchemaNamesForVisiblePicker(["public", "sys", "sys_catalog"], {
db_type: "kingbase",
username: "sys",
}),
).toEqual(["public", "sys"]);
});
it("keeps Oracle DIP visible while hiding default system schemas", () => {
expect(filterSchemaNamesForConnection(["DBX_TEST", "DIP", "SYSTEM"], { db_type: "oracle", database: "XE" }, "XE")).toEqual(["DBX_TEST", "DIP"]);
});
it("keeps the Dameng login schema visible while hiding default system schemas", () => {
expect(filterSchemaNamesForConnection(["APP", "SYS", "SYSDBA", "SYSDBO", "SYSAUDITOR"], { db_type: "dameng", username: "SYSDBA" }, "")).toEqual(["APP", "SYSDBA"]);
});
it("hides openGauss system schemas and prefixes while keeping user schemas", () => {
expect(filterSchemaNamesForVisiblePicker(["blockchain", "cstore", "db4ai", "dbe_perf", "dbe_pldeveloper", "dbe_sql_util", "information_schema", "pg_catalog", "public", "snapshot", "sqladvisor", "xmltype"], { db_type: "opengauss", username: "app_user" })).toEqual(["public"]);
});
it("keeps all schemas visible when show-system-schemas is enabled", () => {
expect(filterSchemaNamesForConnection(["blockchain", "db4ai", "public", "test2", "xmltype"], { db_type: "opengauss", show_system_schemas: true }, "postgres")).toEqual(["blockchain", "db4ai", "public", "test2", "xmltype"]);
});
it("respects explicit visible schema configuration after default filtering", () => {
expect(
filterSchemaNamesForConnection(
["public", "sys_catalog", "reporting"],
{
db_type: "kingbase",
visible_schemas: { test: ["sys_catalog"] },
},
"test",
),
).toEqual(["sys_catalog"]);
});
it("matches prefix-based system schema rules", () => {
expect(isSystemSchemaName("kingbase", "xlog_record_read")).toBe(true);
expect(isSystemSchemaName("opengauss", "dbe_pldeveloper")).toBe(true);
expect(isSystemSchemaName("kingbase", "public")).toBe(false);
});
});
@@ -1,5 +1,18 @@
import type { ConnectionConfig, DatabaseType } from "@/types/database";
type SystemNameRules = {
exact?: ReadonlySet<string>;
prefixes?: readonly string[];
};
type SchemaFilterOptions = {
showSystemSchemas?: boolean;
};
function schemaFilterShowSystemSchemas(connection: Partial<Pick<ConnectionConfig, "show_system_schemas">> | undefined, options?: SchemaFilterOptions): boolean {
return options?.showSystemSchemas ?? connection?.show_system_schemas === true;
}
const SYSTEM_DATABASE_RULES: Partial<Record<DatabaseType, ReadonlySet<string>>> = {
mysql: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
doris: new Set(["information_schema", "mysql", "performance_schema", "sys"]),
@@ -59,6 +72,67 @@ const SYSTEM_DATABASE_RULES: Partial<Record<DatabaseType, ReadonlySet<string>>>
snowflake: new Set(["snowflake", "snowflake_sample_data"]),
};
const POSTGRES_LIKE_SYSTEM_SCHEMA_RULES: SystemNameRules = {
exact: new Set(["information_schema", "pg_catalog", "pg_toast"]),
prefixes: ["pg_temp_", "pg_toast_temp_"],
};
const SYSTEM_SCHEMA_RULES: Partial<Record<DatabaseType, SystemNameRules>> = {
oracle: {
exact: new Set([
"anonymous",
"appqossys",
"audsys",
"ctxsys",
"dbsnmp",
"dvf",
"dvsys",
"exfsys",
"flows_files",
"gsmadmin_internal",
"mddata",
"mdsys",
"mgmt_view",
"olapsys",
"orddata",
"ordplugins",
"ordsys",
"outln",
"owbsys",
"remote_scheduler_agent",
"si_informtn_schema",
"sys",
"sysback",
"sysdg",
"syskm",
"system",
"wmsys",
"xdb",
"xs$null",
]),
},
dameng: {
exact: new Set(["_sys_statistics", "ctisys", "dba", "sys", "sys_dba", "sys_phm", "sysauditor", "sysdba", "sysdbo", "syssso", "system"]),
},
postgres: POSTGRES_LIKE_SYSTEM_SCHEMA_RULES,
gaussdb: {
exact: new Set(["blockchain", "coverage", "cstore", "db4ai", "dbe_perf", "dbe_pldebugger", "dbe_pldeveloper", "dbe_sql_util", "information_schema", "pg_catalog", "pg_toast", "pkg_service", "snapshot", "sqladvisor", "xmltype"]),
prefixes: ["pg_temp_", "pg_toast_temp_", "dbe_"],
},
kwdb: POSTGRES_LIKE_SYSTEM_SCHEMA_RULES,
opengauss: {
exact: new Set(["blockchain", "coverage", "cstore", "db4ai", "dbe_perf", "dbe_pldebugger", "dbe_pldeveloper", "dbe_sql_util", "information_schema", "pg_catalog", "pg_toast", "pkg_service", "snapshot", "sqladvisor", "xmltype"]),
prefixes: ["pg_temp_", "pg_toast_temp_", "dbe_"],
},
questdb: POSTGRES_LIKE_SYSTEM_SCHEMA_RULES,
kingbase: {
exact: new Set(["anon", "dbms_job", "dbms_scheduler", "dbms_sql", "information_schema", "kdb_schedule", "perf", "pg_bitmapindex", "pg_catalog", "pg_toast", "src_restrict", "sys", "sys_catalog", "sys_hm", "sysaudit", "sysmac", "wmsys"]),
prefixes: ["dbms_", "pg_temp_", "pg_toast_temp_", "sys_temp_", "sys_toast_temp_", "xlog_"],
},
highgo: POSTGRES_LIKE_SYSTEM_SCHEMA_RULES,
vastbase: POSTGRES_LIKE_SYSTEM_SCHEMA_RULES,
};
export function visibleDatabaseFilterIsEnabled(visibleDatabases: string[] | undefined): boolean {
return Array.isArray(visibleDatabases);
}
@@ -88,6 +162,15 @@ export function isSystemDatabaseName(databaseType: DatabaseType | undefined, dat
return SYSTEM_DATABASE_RULES[databaseType]?.has(databaseName.toLowerCase()) ?? false;
}
export function isSystemSchemaName(databaseType: DatabaseType | undefined, schemaName: string): boolean {
if (!databaseType) return false;
const normalized = schemaName.toLowerCase();
const rules = SYSTEM_SCHEMA_RULES[databaseType];
if (!rules) return false;
if (rules.exact?.has(normalized)) return true;
return rules.prefixes?.some((prefix) => normalized.startsWith(prefix)) ?? false;
}
export function filterDatabaseNamesForConnection(databaseNames: string[], connection: Pick<ConnectionConfig, "db_type" | "driver_profile" | "visible_databases"> | undefined): string[] {
const visibleDatabases = connection?.visible_databases;
if (visibleDatabaseFilterIsEnabled(visibleDatabases)) {
@@ -103,9 +186,10 @@ export function filterDatabaseNamesForVisiblePicker(databaseNames: string[], con
return databaseNames.filter((name) => !isSystemDatabaseName(connection?.db_type, name));
}
export function filterSchemaNamesForVisiblePicker(schemaNames: string[], connection: Partial<Pick<ConnectionConfig, "db_type" | "username">> | undefined): string[] {
export function filterSchemaNamesForVisiblePicker(schemaNames: string[], connection: Partial<Pick<ConnectionConfig, "db_type" | "username" | "show_system_schemas">> | undefined, options?: SchemaFilterOptions): string[] {
if (schemaFilterShowSystemSchemas(connection, options)) return schemaNames;
const currentSchema = connection?.username?.trim().toLowerCase();
return schemaNames.filter((name) => name.toLowerCase() === currentSchema || !isSystemDatabaseName(connection?.db_type, name));
return schemaNames.filter((name) => name.toLowerCase() === currentSchema || !isSystemSchemaName(connection?.db_type, name));
}
export function connectionUsesVisibleSchemaFilter(connection: Pick<ConnectionConfig, "db_type"> | undefined): boolean {
@@ -116,13 +200,18 @@ export function visibleSchemaFilterIsEnabled(visibleSchemas: Record<string, stri
return Array.isArray(visibleSchemas?.[database]);
}
export function filterSchemaNamesForConnection(schemaNames: string[], connection: (Pick<ConnectionConfig, "db_type" | "visible_schemas" | "visible_databases"> & Partial<Pick<ConnectionConfig, "username">>) | undefined, database: string): string[] {
export function filterSchemaNamesForConnection(
schemaNames: string[],
connection: (Pick<ConnectionConfig, "db_type" | "visible_schemas" | "visible_databases" | "show_system_schemas"> & Partial<Pick<ConnectionConfig, "username">>) | undefined,
database: string,
options?: SchemaFilterOptions,
): string[] {
const visibleSchemas = connection?.visible_schemas;
if (!visibleSchemaFilterIsEnabled(visibleSchemas, database)) {
if (connectionUsesVisibleSchemaFilter(connection) && visibleDatabaseFilterIsEnabled(connection?.visible_databases)) {
return filterVisibleDatabaseNames(schemaNames, connection?.visible_databases);
}
return filterSchemaNamesForVisiblePicker(schemaNames, connection);
return filterSchemaNamesForVisiblePicker(schemaNames, connection, options);
}
const visible = new Set(visibleSchemas![database]);
return schemaNames.filter((name) => visible.has(name));
@@ -543,6 +543,64 @@ describe("connectionStore metadata loading", () => {
expect(store.treeNodes[0]?.children?.[0]?.children?.map((node) => node.label)).toEqual(["public", "tree.extensions"]);
});
it.each(["opengauss", "kingbase"] as const)("reloads %s sidebar schemas when system visibility changes", async (dbType) => {
const listSchemaInfos = vi.fn().mockResolvedValue([
{ name: "information_schema", comment: null },
{ name: "pg_catalog", comment: null },
{ name: "public", comment: null },
]);
const loadSchemaCache = vi.fn().mockResolvedValue(null);
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
listSchemaInfos,
loadSchemaCache,
saveConnections: vi.fn().mockResolvedValue(undefined),
saveSchemaCache: vi.fn().mockResolvedValue(undefined),
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
}));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
const connection = { ...postgresConnection(), id: `${dbType}-1`, db_type: dbType, show_system_schemas: false } as ConnectionConfig;
store.connections = [connection];
store.connectedIds.add(connection.id);
store.treeNodes = [
{
id: connection.id,
label: connection.name,
type: "connection",
connectionId: connection.id,
isExpanded: true,
children: [
{
id: `${connection.id}:app`,
label: "app",
type: "database",
connectionId: connection.id,
database: "app",
isExpanded: false,
children: [],
},
],
},
];
const databaseNode = store.treeNodes[0]!.children![0]!;
await store.loadSchemas(connection.id, "app");
expect(databaseNode.children?.map((node) => node.label).filter((label) => label !== "tree.extensions")).toEqual(["public"]);
store.connections[0]!.show_system_schemas = true;
databaseNode.children = [];
databaseNode.isExpanded = false;
await store.loadSchemas(connection.id, "app");
expect(loadSchemaCache.mock.calls.map(([key]) => key)).toEqual([`${connection.id}:app:schemas-v3:hide-system`, `${connection.id}:app:schemas-v3:show-system`]);
expect(databaseNode.children?.map((node) => node.label).filter((label) => label !== "tree.extensions")).toEqual(["information_schema", "pg_catalog", "public"]);
});
it("clears a failed metadata warning when the driver hint finishes during retry", async () => {
let resolveAgents!: (drivers: Array<{ db_type: string; installed: boolean; update_available: boolean }>) => void;
let resolveSchemas!: (schemas: Array<{ name: string; comment: null }>) => void;
+11 -7
View File
@@ -1028,6 +1028,7 @@ export const useConnectionStore = defineStore("connection", () => {
attached_databases: Array.isArray(config.attached_databases) ? config.attached_databases.filter((database) => database.name?.trim() && database.path?.trim()) : [],
init_script: config.init_script?.trim() ? config.init_script : undefined,
transport_layers: Array.isArray(config.transport_layers) ? config.transport_layers : [],
show_system_schemas: config.show_system_schemas === true,
connect_timeout_secs: config.connect_timeout_secs || 10,
query_timeout_secs: config.query_timeout_secs ?? 30,
idle_timeout_secs: config.idle_timeout_secs ?? 60,
@@ -2870,7 +2871,8 @@ export const useConnectionStore = defineStore("connection", () => {
} else if (config && connectionUsesVisibleSchemaFilter(config)) {
const schemaFilterConfig = config;
const effectiveDb = schemaFilterConfig.database || "";
const cacheKey = schemaCacheKey(connectionId, effectiveDb, config.db_type === "oracle" ? "schemas-v2" : "schemas");
const showSystemSchemas = schemaFilterConfig.show_system_schemas === true;
const cacheKey = schemaCacheKey(connectionId, effectiveDb, config.db_type === "oracle" ? "schemas-v2" : "schemas", showSystemSchemas ? "show-system" : "hide-system");
if (!options?.force) {
const cached = await loadPersistedTreeChildren(node, cacheKey, load);
if (cached.hit) {
@@ -2879,7 +2881,7 @@ export const useConnectionStore = defineStore("connection", () => {
}
}
const schemas = await withMetadataLoadTimeout(connectionId, api.listSchemas(connectionId, effectiveDb, true), "schemas");
const visibleSchemas = filterSchemaNamesForConnection(schemas, schemaFilterConfig, effectiveDb || "");
const visibleSchemas = filterSchemaNamesForConnection(schemas, schemaFilterConfig, effectiveDb || "", { showSystemSchemas });
const schemaNodes: TreeNode[] = sortSidebarNames(visibleSchemas).map((s) => ({
id: `${connectionId}:${s}:${s}`,
label: s,
@@ -3497,7 +3499,8 @@ export const useConnectionStore = defineStore("connection", () => {
await ensureConnected(connectionId);
load = reclaimTreeNodeLoad(load, node);
if (useCachedChildren(node, options, load)) return;
const cacheKey = schemaCacheKey(connectionId, database, "schemas-v3");
const showSystemSchemas = getConfig(connectionId)?.show_system_schemas === true;
const cacheKey = schemaCacheKey(connectionId, database, "schemas-v3", showSystemSchemas ? "show-system" : "hide-system");
if (!options?.force) {
const cached = await loadPersistedTreeChildren(node, cacheKey, load);
if (cached.hit) {
@@ -3512,6 +3515,7 @@ export const useConnectionStore = defineStore("connection", () => {
schemas.map((schema) => schema.name),
getConfig(connectionId),
database,
{ showSystemSchemas },
),
);
const children: TreeNode[] = schemas
@@ -3567,7 +3571,9 @@ export const useConnectionStore = defineStore("connection", () => {
load = reclaimTreeNodeLoad(load, node);
if (useCachedChildren(node, options, load)) return;
const simpleObjectDisplay = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
const cacheKey = schemaCacheKey(connectionId, database, simpleObjectDisplay ? "sqlserver-schemas-simple-v4" : "sqlserver-schemas-grouped-v4");
const config = getConfig(connectionId);
const showSystemSchemas = config?.show_system_schemas === true;
const cacheKey = schemaCacheKey(connectionId, database, simpleObjectDisplay ? "sqlserver-schemas-simple-v4" : "sqlserver-schemas-grouped-v4", showSystemSchemas ? "show-system" : "hide-system");
if (!options?.force) {
const cached = await loadPersistedTreeChildren(node, cacheKey, load);
if (cached.hit) {
@@ -3575,9 +3581,7 @@ export const useConnectionStore = defineStore("connection", () => {
return;
}
}
const config = getConfig(connectionId);
const schemas = filterSchemaNamesForConnection(await api.listSchemas(connectionId, database), config, database);
const schemas = filterSchemaNamesForConnection(await api.listSchemas(connectionId, database), config, database, { showSystemSchemas });
const children = buildSqlServerDatabaseTreeNodes(connectionId, database, schemas);
if (isSidebarSearchQueryChanged(options)) return;
const targetNode = treeNodeLoadTarget(load);
+1
View File
@@ -131,6 +131,7 @@ export interface ConnectionConfig {
database?: string;
visible_databases?: string[];
visible_schemas?: Record<string, string[]>;
show_system_schemas?: boolean;
attached_databases?: AttachedDatabaseConfig[];
init_script?: string;
color?: string;