mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 08:13:34 +00:00
fix(xugu): place public synonyms after schemas
This commit is contained in:
+11
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendTableTreeLoadMoreNode, buildGroupedObjectTreeNodes, buildSimpleObjectTreeNodes, buildTableTreeNodes, mergeTableInfosIntoObjects, mergeTableTreePageChildren, tablePartitionGroups, withoutTableTreeLoadMoreNodes } from "@/lib/table/tableTree";
|
||||
import { appendTableTreeLoadMoreNode, buildGroupedObjectTreeNodes, buildObjectGroupPlaceholderNodes, buildSimpleObjectTreeNodes, buildTableTreeNodes, mergeTableInfosIntoObjects, mergeTableTreePageChildren, tablePartitionGroups, withoutTableTreeLoadMoreNodes } from "@/lib/table/tableTree";
|
||||
import type { ObjectInfo, TableInfo, TreeNode } from "@/types/database";
|
||||
|
||||
const context = {
|
||||
@@ -117,6 +117,16 @@ describe("PostgreSQL custom type metadata", () => {
|
||||
});
|
||||
|
||||
describe("programmable database objects", () => {
|
||||
it("renders only the synonym group for the Xugu public-synonym scope", () => {
|
||||
const groups = buildObjectGroupPlaceholderNodes({
|
||||
...context,
|
||||
schema: "\u0000DBX_XUGU_PUBLIC_SYNONYMS",
|
||||
objectTypes: ["SYNONYM"],
|
||||
});
|
||||
|
||||
expect(groups).toEqual([expect.objectContaining({ type: "group-synonyms", label: "tree.synonyms" })]);
|
||||
});
|
||||
|
||||
it("coalesces Xugu package specification and body into one top-level node", () => {
|
||||
const objects: ObjectInfo[] = [
|
||||
{ name: "DBX_UI_PKG", object_type: "PACKAGE", schema: "APP", valid: true },
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { isXuguPublicSynonymScope, isXuguPublicSynonymTreeNode, XUGU_PUBLIC_SYNONYM_SCOPE, xuguSchemaDisplayName } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { isXuguPublicSynonymScope, isXuguPublicSynonymTreeNode, sortXuguSchemaInfos, XUGU_PUBLIC_SYNONYM_SCOPE, xuguSchemaDisplayName } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { compareSidebarNames } from "@/lib/database/databaseTree";
|
||||
|
||||
describe("Xugu public synonym scope", () => {
|
||||
it("uses an independent protocol identity", () => {
|
||||
@@ -22,4 +23,20 @@ describe("Xugu public synonym scope", () => {
|
||||
expect(isXuguPublicSynonymTreeNode("postgres", "schema", XUGU_PUBLIC_SYNONYM_SCOPE)).toBe(false);
|
||||
expect(isXuguPublicSynonymTreeNode("xugu", "database", XUGU_PUBLIC_SYNONYM_SCOPE)).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the public synonym scope after every real schema", () => {
|
||||
const schemas = [
|
||||
{ name: XUGU_PUBLIC_SYNONYM_SCOPE, comment: null },
|
||||
{ name: "SYSDBA", comment: null },
|
||||
{ name: "AppSchema", comment: null },
|
||||
{ name: "GUEST", comment: null },
|
||||
];
|
||||
|
||||
expect(sortXuguSchemaInfos(schemas, compareSidebarNames).map((schema) => schema.name)).toEqual(["AppSchema", "GUEST", "SYSDBA", XUGU_PUBLIC_SYNONYM_SCOPE]);
|
||||
});
|
||||
|
||||
it("does not treat the real GUEST schema as a public-synonym scope", () => {
|
||||
const schemas = [{ name: "GUEST" }, { name: XUGU_PUBLIC_SYNONYM_SCOPE }];
|
||||
expect(sortXuguSchemaInfos(schemas, compareSidebarNames).map((schema) => schema.name)).toEqual(["GUEST", XUGU_PUBLIC_SYNONYM_SCOPE]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,6 +6,27 @@
|
||||
export const XUGU_PUBLIC_SYNONYM_SCOPE = "\u0000DBX_XUGU_PUBLIC_SYNONYMS";
|
||||
export const XUGU_PUBLIC_SYNONYM_SCOPE_LABEL = "Public synonyms";
|
||||
|
||||
/**
|
||||
* Keep the synthetic public-synonym scope visually separate from real
|
||||
* schemas. The scope is deliberately sorted after every real schema so a
|
||||
* database-global namespace cannot be mistaken for an ordinary owner/schema.
|
||||
*/
|
||||
export function sortXuguSchemaInfos<T extends { name: string }>(schemas: readonly T[], compareNames: (left: string, right: string) => number): T[] {
|
||||
const realSchemas: T[] = [];
|
||||
const publicSynonymScopes: T[] = [];
|
||||
|
||||
for (const schema of schemas) {
|
||||
if (isXuguPublicSynonymScope(schema.name)) {
|
||||
publicSynonymScopes.push(schema);
|
||||
} else {
|
||||
realSchemas.push(schema);
|
||||
}
|
||||
}
|
||||
|
||||
realSchemas.sort((left, right) => compareNames(left.name, right.name));
|
||||
return [...realSchemas, ...publicSynonymScopes];
|
||||
}
|
||||
|
||||
export function isXuguPublicSynonymScope(schema: string | null | undefined): boolean {
|
||||
return schema === XUGU_PUBLIC_SYNONYM_SCOPE;
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ import { appendConnectionErrorHints, isMysqlMissingPasswordFailure } from "@/lib
|
||||
import { connectionNeedsPasswordPrompt } from "@/lib/connection/connectionPassword";
|
||||
import { appendVisibleDatabaseSelection } from "@/lib/connection/connectionVisibleDatabases";
|
||||
import { buildXuguTypeMemberNodes, isXuguTypeMemberContainer } from "@/lib/sidebar/xuguTypeMembers";
|
||||
import { isXuguPublicSynonymScope, xuguSchemaDisplayName, XUGU_PUBLIC_SYNONYM_SCOPE } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { isXuguPublicSynonymScope, sortXuguSchemaInfos, xuguSchemaDisplayName, XUGU_PUBLIC_SYNONYM_SCOPE } from "@/lib/sidebar/xuguPublicSynonyms";
|
||||
import { filterNacosNamespacesForSidebar, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility";
|
||||
import { buildPackageMemberNodes, markPackageNodesExpandable } from "@/lib/sidebar/packageMembers";
|
||||
import { configuredDatabaseProductName, connectionConfigFingerprint, normalizeDatabaseConnectionInfo } from "@/lib/connection/connectionDatabaseInfo";
|
||||
@@ -1543,6 +1543,18 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return sidebarObjectKindsForDatabase(dbType);
|
||||
}
|
||||
|
||||
function sidebarObjectTypesForScope(config: ConnectionConfig | undefined, schema?: string): DatabaseObjectTreeKind[] {
|
||||
if (config?.db_type === "xugu" && isXuguPublicSynonymScope(schema)) {
|
||||
return ["SYNONYM"];
|
||||
}
|
||||
return supportedSidebarObjectTypes(config);
|
||||
}
|
||||
|
||||
function objectTreeCacheVersion(config: ConnectionConfig | undefined, schema: string | undefined, baseVersion: string): string {
|
||||
const scopedVersion = config?.db_type === "xugu" && isXuguPublicSynonymScope(schema) ? `${baseVersion}-public-synonyms` : baseVersion;
|
||||
return ownerAwareMetadataCacheVersion(config, scopedVersion);
|
||||
}
|
||||
|
||||
function sortSidebarSchemaInfos(schemas: readonly SchemaInfo[]): SchemaInfo[] {
|
||||
const byName = new Map<string, SchemaInfo>();
|
||||
for (const schema of schemas) {
|
||||
@@ -1550,7 +1562,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (!name) continue;
|
||||
byName.set(name, { name, comment: schema.comment ?? null });
|
||||
}
|
||||
return sortSidebarNames([...byName.keys()]).map((name) => byName.get(name)!);
|
||||
return sortXuguSchemaInfos([...byName.values()], compareSidebarNames);
|
||||
}
|
||||
|
||||
function buildExtensionManagementNode(connectionId: string, database: string): TreeNode {
|
||||
@@ -1570,7 +1582,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const objectTreeProfileCacheKey = driverProfileObjectTreeProfileForConnection(config)?.cacheKey;
|
||||
// objects-v8: object-group listing SQL gained a pg_type branch for
|
||||
// PostgreSQL-family user-defined types; older cached lists miss TYPE nodes.
|
||||
const baseCacheVersion = ownerAwareMetadataCacheVersion(config, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, node.schema, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const cacheVersion = objectTreeProfileCacheKey ? `${baseCacheVersion}:${objectTreeProfileCacheKey}` : baseCacheVersion;
|
||||
return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, cacheVersion);
|
||||
}
|
||||
@@ -4353,7 +4365,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
if (useCachedChildren(node, options, load)) return;
|
||||
const config = getConfig(connectionId);
|
||||
const showSystemSchemas = config?.show_system_schemas === true;
|
||||
const cacheVersion = ownerAwareMetadataCacheVersion(config, config?.db_type === "xugu" ? "schemas-v4" : "schemas-v3");
|
||||
// schemas-v5 invalidates cached children created before the public
|
||||
// synonym scope was placed after all real schemas.
|
||||
const cacheVersion = ownerAwareMetadataCacheVersion(config, config?.db_type === "xugu" ? "schemas-v5" : "schemas-v3");
|
||||
const cacheKey = schemaCacheKey(connectionId, database, cacheVersion, showSystemSchemas ? "show-system" : "hide-system");
|
||||
if (!options?.force) {
|
||||
const cached = await loadPersistedTreeChildren(node, cacheKey, load);
|
||||
@@ -4723,7 +4737,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
async function loadTables(connectionId: string, database: string, schema?: string, options?: LoadTreeOptions) {
|
||||
const configForScope = getConfig(connectionId);
|
||||
const simpleObjectDisplayForScope = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? supportedSidebarObjectTypes(configForScope) : undefined;
|
||||
const objectTypesForScope = simpleObjectDisplayForScope ? sidebarObjectTypesForScope(configForScope, schema) : undefined;
|
||||
const searchFilter = activeTreeLoadSearchFilter(options);
|
||||
const querySchemaForScope = connectionObjectTreeQuerySchema(configForScope, database, schema);
|
||||
const effectiveSchemaForScope = connectionObjectTreeNodeSchema(configForScope, database, schema);
|
||||
@@ -4735,7 +4749,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
});
|
||||
if (!options?.force && simpleObjectDisplayForScope && !searchFilter && !options?.sidebarTableSearchParentId && !tableNameFilterForScope) {
|
||||
const nodeId = schema ? `${connectionId}:${database}:${schema}` : `${connectionId}:${database}`;
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", ownerAwareMetadataCacheVersion(configForScope, "objects-simple-v8"));
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", objectTreeCacheVersion(configForScope, schema, "objects-simple-v8"));
|
||||
if (await hydrateTreeNodeFromCache(findNode(treeNodes.value, nodeId), cacheKey)) {
|
||||
void loadTables(connectionId, database, schema, { ...options, force: true }).catch(() => undefined);
|
||||
return;
|
||||
@@ -4769,7 +4783,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const searchFilter = activeTreeLoadSearchFilter(options);
|
||||
const config = getConfig(connectionId);
|
||||
const objectTreeProfile = driverProfileObjectTreeProfileForConnection(config);
|
||||
const baseCacheVersion = ownerAwareMetadataCacheVersion(config, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const isPublicSynonymScope = config?.db_type === "xugu" && isXuguPublicSynonymScope(schema);
|
||||
const baseCacheVersion = objectTreeCacheVersion(config, schema, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const cacheVersion = !simpleObjectDisplay && objectTreeProfile?.cacheKey ? `${baseCacheVersion}:${objectTreeProfile.cacheKey}` : baseCacheVersion;
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", cacheVersion);
|
||||
const querySchema = connectionObjectTreeQuerySchema(config, database, schema);
|
||||
@@ -4789,10 +4804,10 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
|
||||
const nonTableObjectTypes = simpleObjectDisplay ? supportedSidebarObjectTypes(config).filter((objectType) => objectType !== "TABLE") : [];
|
||||
const nonTableObjectTypes = simpleObjectDisplay ? sidebarObjectTypesForScope(config, schema).filter((objectType) => objectType !== "TABLE") : [];
|
||||
let children: TreeNode[];
|
||||
let nextObjectCount: number | undefined;
|
||||
if (simpleObjectDisplay) {
|
||||
if (simpleObjectDisplay && !isPublicSynonymScope) {
|
||||
const pageSize = sidebarObjectGroupPageSize();
|
||||
const page = await loadPagedSimpleTableChildren({
|
||||
nodeId,
|
||||
@@ -4808,13 +4823,19 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
});
|
||||
children = page.hasMore && !searchFilter ? appendTableTreeLoadMoreNode(page.children, buildLoadMoreNode(node, page.nextOffset, pageSize), page.loadMoreParent) : page.children;
|
||||
nextObjectCount = page.objectCount;
|
||||
} else if (simpleObjectDisplay) {
|
||||
// The synthetic public scope contains no tables. Avoid issuing a
|
||||
// table-list query against the protocol namespace; supplemental
|
||||
// object loading below will add only its SYNONYM entries.
|
||||
children = [];
|
||||
nextObjectCount = 0;
|
||||
} else {
|
||||
children = buildObjectGroupPlaceholderNodes({
|
||||
nodeId,
|
||||
connectionId,
|
||||
database,
|
||||
schema: effectiveSchema,
|
||||
objectTypes: supportedSidebarObjectTypes(config),
|
||||
objectTypes: sidebarObjectTypesForScope(config, schema),
|
||||
groupOverrides: objectTreeProfile?.groupOverrides,
|
||||
});
|
||||
if (!schema && isPostgresLikeForExtensions(config?.db_type)) {
|
||||
|
||||
Reference in New Issue
Block a user