feat(postgres): add extension management

This commit is contained in:
amwps290 authored and GitHub committed 2026-07-22 15:08:51 +08:00
1 parent 63dd0e88b5
commit 52499f4c9f
13 files changed
+426 -122

No files matched your search

@@ -262,7 +262,7 @@ describe("connectionStore metadata loading", () => {
await store.loadSchemas(connection.id, "app", { force: true });
expect(store.connectionErrors[connection.id]).toBeUndefined();
expect(store.treeNodes[0]?.children?.[0]?.children?.map((node) => node.label)).toEqual(["public"]);
expect(store.treeNodes[0]?.children?.[0]?.children?.map((node) => node.label)).toEqual(["public", "tree.extensions"]);
});
it("clears a failed metadata warning when the driver hint finishes during retry", async () => {
+51 -44
View File
@@ -1111,6 +1111,18 @@ export const useConnectionStore = defineStore("connection", () => {
return sortSidebarNames([...byName.keys()]).map((name) => byName.get(name)!);
}
function buildExtensionManagementNode(connectionId: string, database: string): TreeNode {
return {
id: `${connectionId}:${database}:__extensions`,
label: "tree.extensions",
type: "group-extensions",
connectionId,
database,
isExpanded: false,
children: [],
};
}
function objectGroupCacheKey(node: TreeNode): string {
return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, "objects-v5");
}
@@ -2840,7 +2852,7 @@ export const useConnectionStore = defineStore("connection", () => {
try {
await ensureConnected(connectionId);
if (useCachedChildren(node, options)) return;
const cacheKey = schemaCacheKey(connectionId, database, "schemas-v2");
const cacheKey = schemaCacheKey(connectionId, database, "schemas-v3");
if (!options?.force) {
const cached = await loadPersistedTreeChildren(node, cacheKey);
if (cached.hit) {
@@ -2857,7 +2869,7 @@ export const useConnectionStore = defineStore("connection", () => {
database,
),
);
const children = schemas
const children: TreeNode[] = schemas
.filter((schema) => visibleSchemaNames.has(schema.name))
.map((schema) => {
const s = schema.name;
@@ -2873,6 +2885,9 @@ export const useConnectionStore = defineStore("connection", () => {
children: [],
};
});
if (isPostgresLikeForExtensions(getConfig(connectionId)?.db_type)) {
children.push(buildExtensionManagementNode(connectionId, database));
}
if (isSidebarSearchQueryChanged(options)) return;
if (!canApplyTreeMetadataResult(node)) return;
setChildren(node, children);
@@ -3190,7 +3205,7 @@ export const useConnectionStore = defineStore("connection", () => {
await ensureConnected(connectionId);
if (useCachedChildren(node, options)) return;
const simpleObjectDisplay = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
const cacheKey = schemaCacheKey(connectionId, database, schema || "", simpleObjectDisplay ? "objects-simple-v5" : "objects-grouped-v5");
const cacheKey = schemaCacheKey(connectionId, database, schema || "", simpleObjectDisplay ? "objects-simple-v5" : "objects-grouped-v6");
const searchFilter = activeTreeLoadSearchFilter(options);
const isSidebarTableSearch = !!options?.sidebarTableSearchParentId;
if (!options?.force && !searchFilter) {
@@ -3231,17 +3246,8 @@ export const useConnectionStore = defineStore("connection", () => {
schema: effectiveSchema,
objectTypes: supportedSidebarObjectTypes(config),
});
if (isPostgresLikeForExtensions(config?.db_type)) {
children.push({
id: `${nodeId}:__extensions`,
label: "tree.extensions",
type: "group-extensions",
connectionId,
database,
schema: effectiveSchema,
isExpanded: false,
children: [],
});
if (!schema && isPostgresLikeForExtensions(config?.db_type)) {
children.push(buildExtensionManagementNode(connectionId, database));
}
}
if (isTreeLoadSearchChanged(searchFilter, options)) return;
@@ -3371,35 +3377,6 @@ export const useConnectionStore = defineStore("connection", () => {
);
}
async function loadExtensions(connectionId: string, database: string, schema: string) {
const node = findNode(treeNodes.value, `${connectionId}:${database}:${schema}:__extensions`);
if (!node) return;
node.isLoading = true;
try {
await ensureConnected(connectionId);
if (useCachedChildren(node)) return;
const extensions = await withMetadataLoadTimeout(connectionId, api.listExtensions(connectionId, database, schema), "extensions");
const children: TreeNode[] = extensions.map((ext) => ({
id: `${node.id}:${ext.name}`,
label: ext.name,
type: "extension" as const,
connectionId,
database,
schema,
comment: ext.comment ?? null,
meta: ext,
isExpanded: false,
}));
setChildren(node, children);
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
}
}
async function loadMoreObjectGroupChildren(node: TreeNode) {
if (node.type !== "load-more" || !node.loadMore) return;
const loadMore = node.loadMore;
@@ -3508,6 +3485,36 @@ export const useConnectionStore = defineStore("connection", () => {
);
}
async function loadExtensions(connectionId: string, database: string) {
const node = findNode(treeNodes.value, `${connectionId}:${database}:__extensions`);
if (!node) return;
node.isLoading = true;
try {
await ensureConnected(connectionId);
if (useCachedChildren(node)) return;
const extensions = await withMetadataLoadTimeout(connectionId, api.listExtensions(connectionId, database), "extensions");
const children: TreeNode[] = extensions.map((ext) => ({
id: `${node.id}:${ext.schema || ""}:${ext.name}`,
label: ext.name,
type: "extension" as const,
connectionId,
database,
schema: ext.schema ?? undefined,
comment: ext.comment ?? null,
meta: ext,
isExpanded: false,
}));
setChildren(node, children);
node.objectCount = children.length;
node.isExpanded = true;
} catch (e) {
recordMetadataLoadError(connectionId, e);
throw e;
} finally {
node.isLoading = false;
}
}
async function loadTableForLocate(target: LocateTableTarget): Promise<boolean> {
const config = getConfig(target.connectionId);
if (!config) return false;
@@ -3987,7 +3994,7 @@ export const useConnectionStore = defineStore("connection", () => {
} else if (node.type === "group-partitions") {
node.isExpanded = true;
} else if (node.type === "group-extensions" && node.connectionId && hasTreeNodeDatabaseContext(node)) {
await loadExtensions(node.connectionId, node.database || "", node.schema || "");
await loadExtensions(node.connectionId, node.database || "");
}
}