feat(xugu): expose tablespaces and datafiles in tree

This commit is contained in:
Elias authored and GitHub committed 2026-09-01 00:34:12 +08:00
1 parent afe3b3f3c2
commit 351a8b699b
32 files changed
+811 -4

No files matched your search

@@ -685,6 +685,8 @@ const groupTypes: Set<TreeNodeType> = new Set([
"group-types",
"group-partitions",
"group-extensions",
"group-tablespaces",
"group-datafiles",
]);
function isGroupLabel(node: TreeNode): boolean {
@@ -807,6 +809,35 @@ async function toggle(requestId = beginNavigationRequest()) {
return;
}
// Xugu tablespace rows and their datafile groups are eagerly materialized
// with the tablespace listing; expanding them is a pure local toggle.
if (node.type === "tablespace" || node.type === "group-datafiles") {
node.isExpanded = !node.isExpanded;
emitNodeToggled(node, wasExpanded);
return;
}
if (node.type === "group-tablespaces") {
if (connectionStore.canUseLoadedTreeNodeToggle(node)) {
node.isExpanded = !node.isExpanded;
if (wasExpanded && shouldReleaseCollapsedTreeNodeChildren()) connectionStore.releaseCollapsedTreeNodeChildren(node.id);
emitNodeToggled(node, wasExpanded);
return;
}
try {
await connectionStore.loadXuguTablespaces(node, { preserveCollapsedChildren: !shouldReleaseCollapsedTreeNodeChildren() });
emitNodeToggled(node, wasExpanded);
} catch (e: any) {
if (!isCurrentNavigationRequest(requestId)) return;
if (!wasExpanded) node.isExpanded = false;
const errMsg = e?.message || String(e);
if (errMsg.includes(CONNECTION_ATTEMPT_CANCELLED_MESSAGE)) return;
toast(t("connection.connectFailed", { message: translateBackendError(t, e) }), 5000);
openDriverStoreForInstallError(errMsg);
}
return;
}
if (isXuguTypeMemberContainer(node, connectionStore.getConfig(node.connectionId || "")?.db_type)) {
await connectionStore.loadXuguTypeMembers(node, { preserveCollapsedChildren: !shouldReleaseCollapsedTreeNodeChildren() });
emitNodeToggled(node, wasExpanded);
@@ -240,6 +240,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-amber-500" };
case "database":
return { icon: Database, colorClass: "text-yellow-500" };
case "tablespace":
return { icon: Database, colorClass: "text-orange-500" };
case "datafile":
return { icon: FileCode, colorClass: "text-slate-500" };
case "linked-server-root":
return { icon: Network, colorClass: "text-blue-500" };
case "linked-server":
@@ -395,6 +399,10 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
case "group-extensions":
return { icon: Package, colorClass: "text-violet-500" };
case "group-tablespaces":
return { icon: Database, colorClass: "text-orange-500" };
case "group-datafiles":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-slate-500" };
case "extension":
return { icon: Package, colorClass: "text-violet-400" };
case "load-more":
@@ -432,6 +440,7 @@ function displayLabel(node: TreeNode): string {
function treeNodeSecondaryValue(node: TreeNode): string | undefined {
if (node.type === "type" && node.customTypeKind) return t(`customType.kinds.${node.customTypeKind}`);
if (node.type === "type-member") return (node.meta as CustomTypeTreeMemberMeta | undefined)?.displayValue;
if (node.type === "datafile") return node.xuguDatafilePath;
return undefined;
}
@@ -21,6 +21,9 @@ const connectionStore = {
loadObjectGroupChildren: vi.fn(async (node: TreeNode) => {
node.isExpanded = true;
}),
loadXuguTablespaces: vi.fn(async (node: TreeNode) => {
node.isExpanded = true;
}),
};
const queryStore = {
@@ -421,4 +424,62 @@ describe("SidebarTreeRuntimeHost expansion", () => {
expect(packageFunctionGroup.isExpanded).toBe(true);
expect(packageFunctionGroup.children).toHaveLength(401);
});
it("toggles Xugu tablespace and datafile group nodes locally", async () => {
const datafileGroup: TreeNode = {
id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafiles",
label: "tree.datafiles",
type: "group-datafiles",
parentType: "tablespace",
parentName: "SYSTEM",
connectionId: "xugu",
database: "SHOP_DEMO",
objectCount: 2,
isExpanded: false,
children: [
{ id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafile:1", label: "system_01.dbf", type: "datafile", parentType: "tablespace", parentName: "SYSTEM", connectionId: "xugu", database: "SHOP_DEMO", objectName: "system_01.dbf" },
{ id: "xugu:SHOP_DEMO:tablespace:SYSTEM:datafile:2", label: "system_02.dbf", type: "datafile", parentType: "tablespace", parentName: "SYSTEM", connectionId: "xugu", database: "SHOP_DEMO", objectName: "system_02.dbf" },
],
};
const tablespaceNode: TreeNode = {
id: "xugu:SHOP_DEMO:tablespace:SYSTEM",
label: "SYSTEM",
type: "tablespace",
connectionId: "xugu",
database: "SHOP_DEMO",
objectName: "SYSTEM",
isExpanded: false,
children: [datafileGroup],
};
connectionStore.treeNodes = [tablespaceNode];
connectionStore.getConfig.mockReturnValue({ db_type: "xugu" });
connectionStore.canUseLoadedTreeNodeToggle.mockReturnValue(false);
const host = ref<InstanceType<typeof SidebarTreeRuntimeHost> | null>(null);
const toggled = vi.fn();
const app = createApp(
defineComponent({
setup: () => () => h(SidebarTreeRuntimeHost, { ref: host, node: tablespaceNode, depth: 0, onNodeToggled: toggled }),
}),
);
mountedApps.push(app);
const container = document.createElement("div");
document.body.append(container);
app.use(i18n);
app.mount(container);
host.value?.toggleNode(tablespaceNode);
await nextTick();
expect(tablespaceNode.isExpanded).toBe(true);
expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled();
expect(toggled).toHaveBeenCalledWith(tablespaceNode, true);
host.value?.toggleNode(datafileGroup);
await nextTick();
expect(datafileGroup.isExpanded).toBe(true);
expect(connectionStore.loadXuguTablespaces).not.toHaveBeenCalled();
expect(toggled).toHaveBeenCalledWith(datafileGroup, true);
});
});
+2
View File
@@ -3369,6 +3369,8 @@ export default {
damengUsers: "Users",
damengRoles: "Roles",
damengJobAdmin: "Agent Jobs",
xuguTablespaces: "Storage Spaces",
xuguDatafiles: "Files",
linkedServers: "Linked Servers",
defaultDatabase: "Default DB",
columns: "Columns",
+2
View File
@@ -3277,6 +3277,8 @@ export default withEnglishFallback({
damengUsers: "Usuarios",
damengRoles: "Roles",
damengJobAdmin: "Trabajos del agente",
xuguTablespaces: "Espacios de almacenamiento",
xuguDatafiles: "Archivos",
linkedServers: "Servidores vinculados",
defaultDatabase: "Base predeterminada",
columns: "Columnas",
+2
View File
@@ -3275,6 +3275,8 @@ export default withEnglishFallback({
damengUsers: "Utenti",
damengRoles: "Ruoli",
damengJobAdmin: "Job agente",
xuguTablespaces: "Spazi di archiviazione",
xuguDatafiles: "File",
linkedServers: "Server Collegati",
defaultDatabase: "DB Predefinito",
columns: "Colonne",
+2
View File
@@ -3301,6 +3301,8 @@ export default withEnglishFallback({
damengUsers: "ユーザー",
damengRoles: "ロール",
damengJobAdmin: "エージェントジョブ",
xuguTablespaces: "ストレージ領域",
xuguDatafiles: "ファイル",
defaultDatabase: "デフォルトDB",
columns: "列",
attributes: "属性",
+2
View File
@@ -3154,6 +3154,8 @@ export default withEnglishFallback({
damengUsers: "사용자",
damengRoles: "역할",
damengJobAdmin: "에이전트 작업",
xuguTablespaces: "저장 공간",
xuguDatafiles: "파일",
linkedServers: "연결된 서버",
defaultDatabase: "기본 DB",
columns: "컬럼",
+2
View File
@@ -3277,6 +3277,8 @@ export default withEnglishFallback({
damengUsers: "Usuários",
damengRoles: "Funções",
damengJobAdmin: "Jobs do agente",
xuguTablespaces: "Espaços de armazenamento",
xuguDatafiles: "Arquivos",
linkedServers: "Servidores Vinculados",
materializedViews: "Visualizações Materializadas",
defaultDatabase: "Banco padrão",
+2
View File
@@ -3290,6 +3290,8 @@ export default withEnglishFallback({
damengUsers: "用户",
damengRoles: "角色",
damengJobAdmin: "代理作业",
xuguTablespaces: "存储空间",
xuguDatafiles: "文件",
linkedServers: "链接服务器",
defaultDatabase: "默认库",
columns: "字段",
+2
View File
@@ -3274,6 +3274,8 @@ export default withEnglishFallback({
damengUsers: "使用者",
damengRoles: "角色",
damengJobAdmin: "代理作業",
xuguTablespaces: "儲存空間",
xuguDatafiles: "檔案",
linkedServers: "連結伺服器",
materializedViews: "具體化檢視",
defaultDatabase: "預設庫",
@@ -0,0 +1,40 @@
import { describe, expect, it } from "vitest";
import { buildXuguTablespaceChildren, xuguDatafileDisplayName } from "@/lib/sidebar/xuguTablespaces";
describe("Xugu tablespace tree", () => {
it("uses the physical file basename while retaining the full path", () => {
expect(xuguDatafileDisplayName("/data/DATA1.DBF", 1)).toBe("DATA1.DBF");
expect(xuguDatafileDisplayName("", 2)).toBe("file-2");
});
it("builds tablespace -> files -> datafile nodes with stable ids", () => {
const parent = { id: "conn:storage", connectionId: "conn", database: "APP", children: [] };
const [node] = buildXuguTablespaceChildren(parent, [
{
node_id: "1",
space_id: 7,
space_name: "DATA1",
datafile_num: 1,
space_type: "PERMANENT",
datafiles: [{ node_id: "1", space_id: 7, path: "/data/DATA1.DBF", file_no: 1, curr_size: 1024 }],
},
]);
expect(node.id).toBe("conn:storage:tablespace:7");
expect(node.children?.[0].type).toBe("group-datafiles");
expect(node.children?.[0].children?.[0]).toMatchObject({
id: "conn:storage:tablespace:7:files:1:/data/DATA1.DBF",
type: "datafile",
label: "DATA1.DBF",
xuguDatafilePath: "/data/DATA1.DBF",
});
});
it("preserves expanded state on refresh", () => {
const first = buildXuguTablespaceChildren({ id: "conn:storage", connectionId: "conn", database: "APP", children: [] }, [{ node_id: "1", space_id: 7, space_name: "DATA1", datafile_num: 0, space_type: "PERMANENT", datafiles: [] }]);
first[0].isExpanded = true;
first[0].children![0].isExpanded = true;
const second = buildXuguTablespaceChildren({ id: "conn:storage", connectionId: "conn", database: "APP", children: first }, [{ node_id: "1", space_id: 7, space_name: "DATA1", datafile_num: 0, space_type: "PERMANENT", datafiles: [] }]);
expect(second[0].isExpanded).toBe(true);
expect(second[0].children?.[0].isExpanded).toBe(true);
});
});
+1
View File
@@ -151,6 +151,7 @@ export const syncSavedSqlDirectory = forward("syncSavedSqlDirectory");
export const listDatabases = forward("listDatabases");
export const listDatabaseMetadata = forward("listDatabaseMetadata");
export const listDatabaseStorage = forward("listDatabaseStorage");
export const listXuguTablespaces = forward("listXuguTablespaces");
export const getSqlServerCompletionContext = forward("getSqlServerCompletionContext");
export const listDorisCatalogs = forward("listDorisCatalogs");
export const listDorisCatalogDatabases = forward("listDorisCatalogDatabases");
+5
View File
@@ -4,6 +4,7 @@ import type {
DatabaseConnectionInfo,
DatabaseInfo,
DatabaseStorageInfo,
XuguTablespaceInfo,
SqlServerCompletionContext,
SchemaInfo,
LinkedServerInfo,
@@ -796,6 +797,10 @@ export async function listDatabaseStorage(connectionId: string, databases: strin
});
}
export async function listXuguTablespaces(connectionId: string, database?: string): Promise<XuguTablespaceInfo[]> {
return get(`/api/schema/xugu/tablespaces?${qs({ connection_id: connectionId, database })}`);
}
export async function getSqlServerCompletionContext(connectionId: string, database: string): Promise<SqlServerCompletionContext> {
return get(`/api/schema/sqlserver/completion-context?${qs({ connection_id: connectionId, database })}`);
}
+5
View File
@@ -5,6 +5,7 @@ import { normalizeRustMongoCommand, type MongoCommand } from "@/lib/mongo/mongoS
import { ExternalSqlFileTooLargeError } from "@/lib/sql/sqlFileOpen";
import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
import { decodeMeilisearchDocumentPage, decodeMeilisearchSearchResult, type MeilisearchDocumentPage, type MeilisearchDocumentPageWire, type MeilisearchSearchResult, type MeilisearchSearchWireResult } from "@/lib/backend/meilisearchTransport";
import type { XuguTablespaceInfo } from "@/types/database";
import type { CreatedKey, EnqueuedTaskSummary, KeyCreateInput, KeyListItem, KeyPage, KeyUpdateInput, MeilisearchSystemOverview, MeilisearchTask, TaskListInput, TaskPage, TaskSelector } from "@/types/meilisearchManagement";
/** Normalize Tauri rejections once at the public backend boundary. */
@@ -1160,6 +1161,10 @@ export async function listDatabaseStorage(connectionId: string, databases: strin
return invoke("list_database_storage", { connectionId, databases });
}
export async function listXuguTablespaces(connectionId: string, database?: string): Promise<XuguTablespaceInfo[]> {
return invoke("list_xugu_tablespaces", { connectionId, database });
}
export async function getSqlServerCompletionContext(connectionId: string, database: string): Promise<SqlServerCompletionContext> {
return invoke("get_sqlserver_completion_context", { connectionId, database });
}
@@ -39,6 +39,7 @@ const leafTypes: Set<TreeNodeType> = new Set([
"table-search-control",
"load-more",
"extension",
"datafile",
]);
const fullWidthLabelTypes: Set<TreeNodeType> = new Set(["table", "view", "materialized_view", "mongo-collection", "mongo-bucket", "dynamodb-table", "vector-collection", "elasticsearch-index"]);
@@ -13,7 +13,7 @@ export function treeNodeLoadedChildrenContentPresent(node: TreeNode, sidebarObje
// Grouped mode always materializes object-group placeholders; empty means a stale shell.
return sidebarObjectDisplay === "simple";
}
if (objectTypesForGroupNode(node.type) || TABLE_STRUCTURE_GROUP_TYPES.has(node.type) || node.type === "group-extensions") {
if (objectTypesForGroupNode(node.type) || TABLE_STRUCTURE_GROUP_TYPES.has(node.type) || node.type === "group-extensions" || node.type === "group-tablespaces" || node.type === "group-datafiles") {
return true;
}
return childCount > 0;
@@ -22,6 +22,8 @@ const treeGroupNodeTypes = new Set<TreeNodeType>([
"group-types",
"group-partitions",
"group-extensions",
"group-tablespaces",
"group-datafiles",
"type-attributes",
"type-methods",
]);
@@ -15,6 +15,10 @@ export function getTreeNodeIconInfo(node: TreeNode): TreeNodeIconInfo | null {
case "database":
case "mongo-db":
return { icon: Database, colorClass: "text-yellow-500" };
case "tablespace":
return { icon: Database, colorClass: "text-orange-500" };
case "datafile":
return { icon: FileCode, colorClass: "text-slate-500" };
case "vector-database":
return { icon: Database, colorClass: "text-cyan-500" };
case "linked-server-root":
@@ -138,6 +142,10 @@ export function getTreeNodeIconInfo(node: TreeNode): TreeNodeIconInfo | null {
return { icon: Clock, colorClass: "text-orange-400" };
case "group-packages":
return { icon: Package, colorClass: "text-cyan-500" };
case "group-tablespaces":
return { icon: Database, colorClass: "text-orange-500" };
case "group-datafiles":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-slate-500" };
case "group-partitions":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
case "group-extensions":
@@ -0,0 +1,50 @@
import type { TreeNode, XuguTablespaceInfo } from "@/types/database";
export function xuguDatafileDisplayName(path: string, fileNo: number): string {
const normalized = path.trim().replaceAll("\\", "/");
const parts = normalized.split("/").filter(Boolean);
const basename = parts.length > 0 ? parts[parts.length - 1] : undefined;
return basename || path.trim() || `file-${fileNo}`;
}
export function buildXuguTablespaceChildren(parent: Pick<TreeNode, "id" | "connectionId" | "database" | "children">, tablespaces: readonly XuguTablespaceInfo[]): TreeNode[] {
const existing = new Map((parent.children ?? []).map((child) => [child.id, child] as const));
return tablespaces.map((tablespace) => {
const tablespaceId = `${parent.id}:tablespace:${tablespace.space_id}`;
const previousTablespace = existing.get(tablespaceId);
const filesGroupId = `${tablespaceId}:files`;
const previousFilesGroup = previousTablespace?.children?.find((child) => child.id === filesGroupId);
const fileNodes = tablespace.datafiles.map((file) => ({
id: `${filesGroupId}:${file.file_no}:${file.path}`,
label: xuguDatafileDisplayName(file.path, file.file_no),
type: "datafile" as const,
connectionId: parent.connectionId,
database: parent.database,
objectName: file.path,
xuguDatafilePath: file.path,
comment: file.path,
}));
const filesGroup: TreeNode = {
id: filesGroupId,
label: "tree.xuguDatafiles",
type: "group-datafiles",
connectionId: parent.connectionId,
database: parent.database,
objectCount: fileNodes.length,
isExpanded: previousFilesGroup?.isExpanded ?? false,
children: fileNodes,
};
return {
id: tablespaceId,
label: tablespace.space_name,
type: "tablespace" as const,
connectionId: parent.connectionId,
database: parent.database,
objectName: tablespace.space_name,
xuguTablespace: tablespace,
objectCount: tablespace.datafiles.length,
isExpanded: previousTablespace?.isExpanded ?? false,
children: [filesGroup],
} satisfies TreeNode;
});
}
+70 -2
View File
@@ -164,6 +164,7 @@ import { invalidateObjectBrowserRowsCache } from "@/lib/table/objectBrowserRowsC
import { MetadataTaskLimiter } from "@/lib/metadata/metadataTaskLimiter";
import { buildCustomTypeTreeChildren } from "@/lib/sidebar/customTypeTree";
import { TreeNodeLoadRegistry, type TreeNodeLoadHandle } from "@/lib/metadata/treeNodeLoadHandle";
import { buildXuguTablespaceChildren } from "@/lib/sidebar/xuguTablespaces";
import i18n from "@/i18n";
import type { MqAdminConfig } from "@/types/mq";
import { RABBITMQ_MQ_TENANT, resolveMqSystemKindFromConnection } from "@/lib/mq/mqConsoleDefaults";
@@ -1364,7 +1365,7 @@ export const useConnectionStore = defineStore("connection", () => {
// as metadata children, withConnectionUtilityNodes would keep the old copies
// AND append fresh ones on every useCachedChildren pass, duplicating the
// 用户/角色 menus once per refresh cycle.
return node.type === "user-admin" || node.type === "dameng-users" || node.type === "dameng-roles" || node.type === "dameng-job-admin" || node.type === "saved-sql-root";
return node.type === "user-admin" || node.type === "dameng-users" || node.type === "dameng-roles" || node.type === "dameng-job-admin" || node.type === "group-tablespaces" || node.type === "saved-sql-root";
}
function connectionMetadataChildren(children: TreeNode[] | undefined): TreeNode[] {
@@ -1653,13 +1654,33 @@ export const useConnectionStore = defineStore("connection", () => {
};
}
function buildXuguTablespacesNode(connectionId: string, existingConnectionNode?: TreeNode): TreeNode | undefined {
const config = getConfig(connectionId);
if (effectiveDatabaseTypeForConnection(config) !== "xugu") return undefined;
const existing = existingConnectionNode?.children?.find((child) => child.type === "group-tablespaces");
return {
id: `${connectionId}:__xugu_tablespaces`,
label: "tree.xuguTablespaces",
type: "group-tablespaces",
connectionId,
// SYS_TABLESPACES/SYS_DATAFILES are scoped to the current database. The
// optional value lets the agent switch to the configured database while
// retaining compatibility with connections that have no default DB.
database: config?.database || "",
objectCount: existing?.objectCount,
isExpanded: existing?.isExpanded ?? false,
children: existing?.children ?? [],
};
}
function withConnectionUtilityNodes(connectionId: string, children: TreeNode[], existingConnectionNode?: TreeNode): TreeNode[] {
const nonUtilityChildren = connectionMetadataChildren(children);
const userAdminNode = buildUserAdminNode(connectionId, existingConnectionNode);
const damengUserNode = buildDamengUserNode(connectionId, existingConnectionNode);
const damengRoleNode = buildDamengRoleNode(connectionId, existingConnectionNode);
const damengJobAdminNode = buildDamengJobAdminNode(connectionId, existingConnectionNode);
return [...nonUtilityChildren, userAdminNode, damengUserNode, damengRoleNode, damengJobAdminNode].filter(Boolean) as TreeNode[];
const xuguTablespacesNode = buildXuguTablespacesNode(connectionId, existingConnectionNode);
return [...nonUtilityChildren, userAdminNode, damengUserNode, damengRoleNode, damengJobAdminNode, xuguTablespacesNode].filter(Boolean) as TreeNode[];
}
function withSavedSqlRoot(connectionId: string, children: TreeNode[], existingConnectionNode?: TreeNode): TreeNode[] {
@@ -4137,6 +4158,50 @@ export const useConnectionStore = defineStore("connection", () => {
{ flush: "post" },
);
async function loadXuguTablespaces(node: TreeNode, options?: LoadTreeOptions) {
if (node.type !== "group-tablespaces" || !node.connectionId) return;
const connectionId = node.connectionId;
const config = getConfig(connectionId);
if (effectiveDatabaseTypeForConnection(config) !== "xugu") return;
return runTreeMetadataLoad(
{
kind: "xugu-tablespaces",
connectionId,
database: node.database || undefined,
driverProfile: metadataDriverProfile(config),
},
async () => {
let load = beginTreeNodeLoad(node);
try {
await ensureConnected(connectionId);
load = reclaimTreeNodeLoad(load, node);
if (useCachedChildren(node, options, load)) return;
const tablespaces = await withMetadataLoadTimeout(connectionId, api.listXuguTablespaces(connectionId, node.database || undefined), "Xugu tablespaces");
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
const children = buildXuguTablespaceChildren(targetNode, tablespaces);
setChildren(targetNode, children);
targetNode.objectCount = children.length;
targetNode.isExpanded = true;
} catch (error) {
// Storage metadata is an optional, read-only enhancement. A user
// without SYS_* view access should see an empty group rather than a
// connection-level RPC error that blocks the rest of the tree.
console.debug("[DBX][xugu-tablespaces:unavailable]", { connectionId, error });
const targetNode = treeNodeLoadTarget(load);
if (targetNode) {
setChildren(targetNode, []);
targetNode.objectCount = 0;
targetNode.isExpanded = true;
}
} finally {
finishTreeNodeLoad(load);
}
},
options,
);
}
async function loadDatabases(connectionId: string, options?: LoadTreeOptions) {
const configForScope = getConfig(connectionId);
const searchFilter = activeTreeLoadSearchFilter(options);
@@ -6549,6 +6614,8 @@ export const useConnectionStore = defineStore("connection", () => {
node.isExpanded = true;
} else if (node.type === "doris-catalog" && node.connectionId) {
await loadDorisCatalogDatabases(node, options);
} else if (node.type === "group-tablespaces" && node.connectionId) {
await loadXuguTablespaces(node, options);
} else if (node.type === "database" && node.connectionId && hasTreeNodeDatabaseContext(node)) {
if (node.catalog && node.catalog !== "internal") {
await loadDorisCatalogTables(node, options);
@@ -8863,6 +8930,7 @@ export const useConnectionStore = defineStore("connection", () => {
loadObjectGroupChildren,
loadCustomTypeChildren,
loadPackageMembers,
loadXuguTablespaces,
loadXuguTypeMembers,
loadMoreObjectGroupChildren,
loadAllObjectGroupChildren,
+29
View File
@@ -344,6 +344,29 @@ export interface DatabaseStorageInfo {
size_bytes: number | null;
}
export interface XuguDatafileInfo {
node_id: string;
space_id: number;
path: string;
file_no: number;
max_size?: number | null;
step_size?: number | null;
curr_size?: number | null;
reserved1?: string | null;
}
export interface XuguTablespaceInfo {
node_id: string;
space_id: number;
space_name: string;
datafile_num: number;
space_type: string;
media_error?: string | null;
total_chunk_num?: number | null;
free_chunk_num?: number | null;
datafiles: XuguDatafileInfo[];
}
export interface SqlServerCompletionContext {
default_schema: string;
supports_session_database_switch: boolean;
@@ -862,6 +885,8 @@ export type TreeNodeType =
| "connection"
| "connection-group"
| "database"
| "tablespace"
| "datafile"
| "doris-catalog"
| "linked-server-root"
| "linked-server"
@@ -902,6 +927,8 @@ export type TreeNodeType =
| "group-packages"
| "group-partitions"
| "group-extensions"
| "group-tablespaces"
| "group-datafiles"
| "extension"
| "object-browser"
| "user-admin"
@@ -1001,6 +1028,8 @@ export interface TreeNode {
comment?: string | null;
valid?: boolean | null;
sizeBytes?: number | null;
xuguTablespace?: XuguTablespaceInfo;
xuguDatafilePath?: string;
objectCount?: number;
loadedKeyCount?: number;
totalKeyCount?: number;