feat(xugu): expose package members in schema tree

This commit is contained in:
Elias authored and GitHub committed 2026-08-10 17:05:54 +08:00
1 parent 37f13018d3
commit ad9075e18f
15 files changed
+922 -28

No files matched your search

@@ -12,7 +12,7 @@ import { matchSidebarLabel } from "@/lib/sidebar/sidebarSearch";
import { buildTableTreeNodes } from "@/lib/table/tableTree";
import { isCancelSearchShortcut, isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut, isViewTableDdlShortcut } from "@/lib/editor/keyboardShortcuts";
import { sidebarNodeSupportsDdlView } from "@/lib/sidebar/sidebarTreeDdlShortcut";
import { copyNameForTreeNode, objectSourceKindForTreeNode } from "@/lib/sidebar/treeNodeClick";
import { copyNameForTreeNode, objectSourceTargetForTreeNode } from "@/lib/sidebar/treeNodeClick";
import { copyToClipboard } from "@/lib/common/clipboard";
import { connectionPasteTargetGroupId, copySelectedConnectionsToClipboards, selectedConnectionEditTarget } from "@/lib/sidebar/sidebarConnectionSelection";
import { isEditableSidebarTypeSearchTarget, sidebarTypeSearchNextQuery } from "@/lib/sidebar/sidebarTypeSearch";
@@ -106,7 +106,8 @@ const sidebarDdlDatabaseType = computed(() => {
const connectionId = sidebarDdlTarget.value?.connectionId;
return connectionId ? effectiveDatabaseTypeForConnection(store.getConfig(connectionId)) : undefined;
});
const sidebarObjectSourceType = computed(() => (sidebarObjectSourceTarget.value ? objectSourceKindForTreeNode(sidebarObjectSourceTarget.value.node.type) : null));
const sidebarObjectSourceResolvedTarget = computed(() => (sidebarObjectSourceTarget.value ? objectSourceTargetForTreeNode(sidebarObjectSourceTarget.value.node) : null));
const sidebarObjectSourceType = computed(() => sidebarObjectSourceResolvedTarget.value?.objectType ?? null);
const sidebarObjectSourceDatabaseType = computed(() => {
const connectionId = sidebarObjectSourceTarget.value?.node.connectionId;
return connectionId ? effectiveDatabaseTypeForConnection(store.getConfig(connectionId)) : undefined;
@@ -1336,7 +1337,7 @@ function openSidebarDdlForSelection(): boolean {
}
function openSidebarObjectSource(node: TreeNode, initialEditing: boolean) {
if (!node.connectionId || !node.database || !objectSourceKindForTreeNode(node.type)) return;
if (!node.connectionId || !node.database || !objectSourceTargetForTreeNode(node)) return;
const target = createSidebarActionTarget(node);
const requestGeneration = beginSidebarAction();
void store
@@ -1980,10 +1981,10 @@ defineExpose({ focusSearch, createNewGroup, collapseAllTreeNodes });
v-model:open="sidebarObjectSourceOpen"
:connection-id="sidebarObjectSourceTarget.node.connectionId!"
:database="sidebarObjectSourceTarget.node.database!"
:schema="sidebarObjectSourceTarget.node.schema"
:name="sidebarObjectSourceTarget.node.objectName || sidebarObjectSourceTarget.node.label"
:schema="sidebarObjectSourceResolvedTarget?.schema"
:name="sidebarObjectSourceResolvedTarget!.name"
:relation-name="sidebarObjectSourceTarget.node.tableName"
:signature="sidebarObjectSourceTarget.node.signature"
:signature="sidebarObjectSourceResolvedTarget?.signature"
:object-type="sidebarObjectSourceType"
:database-type="sidebarObjectSourceDatabaseType"
:dialect="sidebarObjectSourceDialect"
@@ -94,7 +94,7 @@ import {
usesTreeSchemaMode,
isSingleDatabase,
} from "@/lib/database/databaseCapabilities";
import { copyNameForTreeNode, isDirectNavigationTreeNode, isDocumentBrowserTreeNode, objectSourceKindForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick";
import { copyNameForTreeNode, isDirectNavigationTreeNode, isDocumentBrowserTreeNode, objectSourceTargetForTreeNode, shouldRunTreeNodeRowAction, treeNodeRowAction, treeNodeRowDoubleClickAction } from "@/lib/sidebar/treeNodeClick";
import { mongoCollectionTableTypeFromNode, mongoDropIndexFailureCount } from "@/lib/sidebar/mongoCollectionMutation";
import { dataTabOpenModeFromTreeClick, type DataTabOpenMode } from "@/lib/sidebar/dataTabOpenPolicy";
import { isCopySidebarSelectionShortcut, isEditSidebarConnectionShortcut, isPasteSidebarSelectionShortcut } from "@/lib/editor/keyboardShortcuts";
@@ -622,6 +622,12 @@ async function toggle() {
return;
}
if (node.type === "package" && node.connectionId && connectionStore.getConfig(node.connectionId)?.db_type === "xugu") {
await connectionStore.loadXuguPackageMembers(node);
emitNodeToggled(node, wasExpanded);
return;
}
if (node.type === "connection" && node.connectionId) {
const config = connectionStore.getConfig(node.connectionId);
if (config?.db_type === "redis") {
@@ -1674,18 +1680,18 @@ function openObjectSourceDialog(initialEditing: boolean) {
if (!node.connectionId || !node.database) return;
const connectionId = node.connectionId;
const database = node.database;
const objectType = objectSourceKindForTreeNode(node.type);
if (!objectType) return;
const sourceTarget = objectSourceTargetForTreeNode(node);
if (!sourceTarget) return;
const openMode = settingsStore.editorSettings.routineSourceOpenMode;
if (openMode === "query-tab") {
void connectionStore
.ensureConnected(connectionId)
.then(async () => {
connectionStore.activeConnectionId = connectionId;
const schema = node.schema || database;
const schema = sourceTarget.schema || database;
const databaseType = effectiveDatabaseTypeForConnection(connectionStore.getConfig(connectionId));
if (!databaseType) throw new Error("Connection type is unavailable.");
const objectName = node.objectName || node.label;
const objectName = sourceTarget.name;
const {
raw,
editableSource,
@@ -1695,7 +1701,7 @@ function openObjectSourceDialog(initialEditing: boolean) {
database,
schema,
name: objectName,
objectType: objectType as any,
objectType: sourceTarget.objectType as any,
databaseType,
signature: node.signature,
});
@@ -4649,14 +4655,15 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
// 8. Procedure / Function / Package
if (node.type === "procedure" || node.type === "function") {
if (node.type === "procedure") {
const isPackageMember = node.parentType === "package" && !!node.parentName;
if (node.type === "procedure" && !isPackageMember) {
items.push({ label: t("contextMenu.executeProcedure"), action: openProcedureExecution, icon: Play });
}
if (currentDatabaseType() === "xugu" && buildXuguCompileSql({ objectType: node.type, schema: node.schema, name: node.objectName || node.label })) {
if (!isPackageMember && currentDatabaseType() === "xugu" && buildXuguCompileSql({ objectType: node.type, schema: node.schema, name: node.objectName || node.label })) {
items.push({ label: t("contextMenu.compileObject"), action: compileXuguObject, icon: Wrench });
}
items.push({ label: t("contextMenu.viewSource"), action: () => openObjectSourceDialog(false), icon: Code2 });
if (canRenameObject.value) {
if (!isPackageMember && canRenameObject.value) {
items.push({
label: t("contextMenu.renameObject"),
action: openRenameObjectDialog,
@@ -4666,14 +4673,16 @@ function buildObjectSidebarMenu(context: SidebarMenuFactoryContext): boolean {
}
items.push({ label: "", separator: true });
items.push({ label: t("contextMenu.changeOpenMode"), action: () => emit("open-settings", "navigation"), icon: Settings2 });
items.push({ label: "", separator: true });
items.push({
label: deleteMenuLabel(node.type === "procedure" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction")),
action: deleteMenuAction(requestDropObject),
icon: Trash2,
shortcut: shortcutDelete,
variant: "destructive" as const,
});
if (!isPackageMember) {
items.push({ label: "", separator: true });
items.push({
label: deleteMenuLabel(node.type === "procedure" ? t("contextMenu.dropProcedure") : t("contextMenu.dropFunction")),
action: deleteMenuAction(requestDropObject),
icon: Trash2,
shortcut: shortcutDelete,
variant: "destructive" as const,
});
}
return true;
}
@@ -558,6 +558,7 @@ const canExpand = computed(() =>
canTreeNodeShowExpander({
type: activeNode.value.type,
childCount: activeNode.value.children?.length ?? 0,
explicitContainer: activeNode.value.children !== undefined,
}),
);
@@ -27,6 +27,13 @@ describe("sidebar tree item layout", () => {
expect(canTreeNodeShowExpander({ type: "consul-overview", childCount: 0 })).toBe(false);
});
it("shows an expander only for package nodes explicitly marked as containers", () => {
expect(canTreeNodeShowExpander({ type: "package", childCount: 0 })).toBe(false);
expect(canTreeNodeShowExpander({ type: "package", childCount: 0, explicitContainer: true })).toBe(true);
expect(canTreeNodeShowExpander({ type: "package-body", childCount: 0, explicitContainer: true })).toBe(false);
expect(canTreeNodeShowExpander({ type: "procedure", childCount: 0, explicitContainer: true })).toBe(false);
});
it("aligns comments to the longest sibling name without crossing parent groups", () => {
const widths = alignedSidebarCommentLabelWidths([
{ id: "tables", depth: 1, alignable: false, hasComment: false, labelWidth: 0 },
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { isDirectNavigationTreeNode, objectSourceKindForTreeNode, shouldActivateTreeNodeOnSingleClick, treeNodeRowAction } from "@/lib/sidebar/treeNodeClick";
import { isDirectNavigationTreeNode, objectSourceKindForTreeNode, objectSourceTargetForTreeNode, shouldActivateTreeNodeOnSingleClick, treeNodeRowAction } from "@/lib/sidebar/treeNodeClick";
describe("treeNodeClick", () => {
it("opens synonym nodes as synonym source", () => {
@@ -17,4 +17,48 @@ describe("treeNodeClick", () => {
expect(treeNodeRowAction("consul-root", false, "double")).toBe("toggle");
expect(treeNodeRowAction("consul-overview", false, "double")).toBe("toggle");
});
it("expands package containers while preserving source behavior for leaf packages", () => {
expect(treeNodeRowAction("package", true)).toBe("toggle");
expect(treeNodeRowAction("package", false)).toBe("open-source");
});
it("routes package members to their owning package body", () => {
expect(
objectSourceTargetForTreeNode({
id: "pkg:member",
label: "calculate(p_value IN INT)",
type: "function",
objectName: "calculate",
parentName: "business_api",
parentSchema: "app_schema",
parentType: "package",
schema: "ignored_schema",
signature: "p_value IN INT",
}),
).toEqual({
name: "business_api",
schema: "app_schema",
objectType: "PACKAGE",
signature: "p_value IN INT",
});
});
it("keeps standalone routine source routing unchanged", () => {
expect(objectSourceTargetForTreeNode({ id: "proc", label: "standalone_proc", type: "procedure", schema: "app" })).toEqual({
name: "standalone_proc",
schema: "app",
objectType: "PROCEDURE",
signature: undefined,
});
});
it("does not reinterpret unrelated parent metadata as a package member", () => {
expect(objectSourceTargetForTreeNode({ id: "routine", label: "child_routine", type: "function", schema: "app", parentName: "other_parent" })).toEqual({
name: "child_routine",
schema: "app",
objectType: "FUNCTION",
signature: undefined,
});
});
});
@@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import type { CompletionAssistantCandidate, TreeNode } from "@/types/database";
import { buildXuguPackageMemberNodes, markXuguPackageNodesExpandable } from "@/lib/sidebar/xuguPackageMembers";
function packageNode(): TreeNode {
return {
id: "conn:db:schema:package:business_api",
label: "business_api",
type: "package",
objectName: "business_api",
connectionId: "conn",
database: "db",
schema: "app_schema",
valid: false,
};
}
describe("Xugu package member tree", () => {
it("marks package specifications as expandable without changing other objects", () => {
const nodes: TreeNode[] = [packageNode(), { id: "body", label: "business_api", type: "package-body" }, { id: "proc", label: "standalone", type: "procedure" }];
const result = markXuguPackageNodesExpandable(nodes);
expect(result[0]?.children).toEqual([]);
expect(result[1]?.children).toBeUndefined();
expect(result[2]?.children).toBeUndefined();
});
it("builds public procedure and function children with overload identities", () => {
const candidates: CompletionAssistantCandidate[] = [
{ name: "calculate", kind: "procedure", signature: "p_value IN INT" },
{ name: "calculate", kind: "procedure", signature: "p_value IN VARCHAR" },
{ name: "lookup", kind: "function", signature: null, data_type: "VARCHAR" },
{ name: "ignored_table", kind: "table" },
];
const result = buildXuguPackageMemberNodes(packageNode(), candidates);
expect(result.map((node) => [node.type, node.label])).toEqual([
["procedure", "calculate(p_value IN INT)"],
["procedure", "calculate(p_value IN VARCHAR)"],
["function", "lookup"],
]);
expect(result.every((node) => node.parentName === "business_api" && node.parentSchema === "app_schema" && node.parentType === "package" && node.valid === false)).toBe(true);
expect(new Set(result.map((node) => node.id)).size).toBe(3);
});
it("deduplicates exact duplicate metadata without merging case-distinct members", () => {
const candidates: CompletionAssistantCandidate[] = [
{ name: "MixedCase", kind: "function", signature: "p INT" },
{ name: "MixedCase", kind: "function", signature: "p INT" },
{ name: "MIXEDCASE", kind: "function", signature: "p INT" },
{ name: "", kind: "procedure" },
];
const result = buildXuguPackageMemberNodes(packageNode(), candidates);
expect(result.map((node) => node.objectName)).toEqual(["MixedCase", "MIXEDCASE"]);
});
});
@@ -155,8 +155,8 @@ export function canTreeNodeExpand(type: TreeNodeType): boolean {
return !leafTypes.has(type);
}
export function canTreeNodeShowExpander({ type, childCount }: { type: TreeNodeType; childCount?: number }): boolean {
if (!canTreeNodeExpand(type)) return false;
export function canTreeNodeShowExpander({ type, childCount, explicitContainer = false }: { type: TreeNodeType; childCount?: number; explicitContainer?: boolean }): boolean {
if (!canTreeNodeExpand(type) && !(type === "package" && explicitContainer)) return false;
if (childCount === 0 && emptyContainerTypes.has(type)) return false;
return true;
}
@@ -58,6 +58,32 @@ export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKin
return null;
}
export interface TreeNodeObjectSourceTarget {
name: string;
schema?: string;
objectType: ObjectSourceKind;
signature?: string;
}
export function objectSourceTargetForTreeNode(node: TreeNode): TreeNodeObjectSourceTarget | null {
if ((node.type === "procedure" || node.type === "function") && node.parentType === "package" && node.parentName) {
return {
name: node.parentName,
schema: node.parentSchema || node.schema,
objectType: "PACKAGE",
signature: node.signature,
};
}
const objectType = objectSourceKindForTreeNode(node.type);
if (!objectType) return null;
return {
name: node.objectName || node.label,
schema: node.schema,
objectType,
signature: node.signature,
};
}
export function isDocumentBrowserTreeNode(type: TreeNodeType): boolean {
return documentBrowserNodeTypes.has(type);
}
@@ -66,6 +92,7 @@ export function treeNodeRowAction(type: TreeNodeType, canExpand: boolean, activa
if (!shouldActivateTreeNodeOnSingleClick(type, activation)) return "none";
if (type === "extension") return "open-extension-details";
if (dataNodeTypes.has(type)) return "open-data";
if (type === "package" && canExpand) return "toggle";
if (sourceNodeTypes.has(type)) return "open-source";
if (toggleLeafNodeTypes.has(type)) return "toggle";
if (canExpand) return "toggle";
@@ -0,0 +1,40 @@
import type { CompletionAssistantCandidate, TreeNode } from "@/types/database";
export function markXuguPackageNodesExpandable(nodes: TreeNode[]): TreeNode[] {
return nodes.map((node) => (node.type === "package" ? { ...node, children: node.children ?? [] } : node));
}
export function buildXuguPackageMemberNodes(packageNode: TreeNode, candidates: readonly CompletionAssistantCandidate[]): TreeNode[] {
const parentName = packageNode.objectName || packageNode.label;
const parentSchema = packageNode.schema;
const seen = new Set<string>();
const children: TreeNode[] = [];
for (const candidate of candidates) {
if (candidate.kind !== "procedure" && candidate.kind !== "function") continue;
const name = candidate.name.trim();
if (!name) continue;
const signature = candidate.signature?.trim() || "";
const key = `${candidate.kind}\0${name}\0${signature}`;
if (seen.has(key)) continue;
seen.add(key);
children.push({
id: `${packageNode.id}:member:${candidate.kind}:${name}:${signature}`,
label: signature ? `${name}(${signature})` : name,
type: candidate.kind,
objectName: name,
signature: signature || undefined,
parentName,
parentSchema,
parentType: "package",
valid: packageNode.valid,
connectionId: packageNode.connectionId,
database: packageNode.database,
schema: parentSchema,
isExpanded: false,
children: undefined,
});
}
return children;
}
@@ -60,6 +60,19 @@ function oracleConnection(): ConnectionConfig {
} as ConnectionConfig;
}
function xuguConnection(): ConnectionConfig {
return {
id: "xugu-1",
name: "XuguDB",
db_type: "xugu",
host: "127.0.0.1",
port: 5138,
username: "app_user",
password: "",
database: "app_db",
} as ConnectionConfig;
}
function genericJdbcConnection(): ConnectionConfig {
return {
id: "jdbc-1",
@@ -132,6 +145,61 @@ describe("connectionStore metadata loading", () => {
setActivePinia(createPinia());
});
// Dynamic import of the merged connectionStore plus the scoped metadata
// load can exceed Vitest's 5s default on slower CI runners; the assertion
// is deterministic, so allow a wider timeout.
it("loads Xugu package members through the scoped completion endpoint", async () => {
const completionAssistantSearch = vi.fn().mockResolvedValue({
candidates: [
{ name: "process_item", kind: "procedure", signature: "p_id IN INT" },
{ name: "process_item", kind: "procedure", signature: "p_code IN VARCHAR" },
{ name: "item_count", kind: "function", data_type: "INT" },
],
incomplete: false,
fallback_used: false,
});
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
vi.doMock("@/lib/backend/api", () => ({ checkConnectionHealth: vi.fn().mockResolvedValue(undefined), completionAssistantSearch }));
const { useConnectionStore } = await import("@/stores/connectionStore");
const store = useConnectionStore();
const connection = xuguConnection();
const packageNode: TreeNode = {
id: "xugu-1:app_db:app_schema:package:business_api",
label: "business_api",
type: "package",
connectionId: connection.id,
database: "app_db",
schema: "app_schema",
children: [],
isExpanded: false,
};
store.connections = [connection];
store.connectedIds = new Set([connection.id]);
store.treeNodes = [packageNode];
await store.loadXuguPackageMembers(packageNode);
expect(completionAssistantSearch).toHaveBeenCalledWith({
connection_id: connection.id,
database: "app_db",
schema: "app_schema",
object_kinds: ["routine"],
mask: "",
case_sensitive: true,
global_search: false,
max_results: 1000,
search_in_comments: false,
search_in_definitions: false,
parent_schema: "app_schema",
parent_name: "business_api",
match_mode: "prefix",
});
expect(packageNode.isExpanded).toBe(true);
expect(packageNode.children?.map((child) => child.label)).toEqual(["process_item(p_id IN INT)", "process_item(p_code IN VARCHAR)", "item_count"]);
expect(packageNode.children?.every((child) => child.parentName === "business_api")).toBe(true);
}, 15000);
it("loads missing database roots only for connected sidebar search targets", async () => {
const checkConnectionHealth = vi.fn().mockResolvedValue(undefined);
const listDatabases = vi.fn().mockResolvedValue([{ name: "dajia", comment: null }]);
+59 -2
View File
@@ -111,6 +111,7 @@ import { appendAgentDriverUpdateHint, hasAgentDriverUpdate, hasInstalledAgentVer
import { appendConnectionErrorHints } from "@/lib/connection/connectionErrorHints";
import { appendVisibleDatabaseSelection } from "@/lib/connection/connectionVisibleDatabases";
import { filterNacosNamespacesForSidebar, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility";
import { buildXuguPackageMemberNodes, markXuguPackageNodesExpandable } from "@/lib/sidebar/xuguPackageMembers";
import { configuredDatabaseProductName, connectionConfigFingerprint, normalizeDatabaseConnectionInfo } from "@/lib/connection/connectionDatabaseInfo";
import { createMetadataLoadTrace, logMetadataLoadTrace, MetadataLoadCoordinator, type MetadataLoadTraceLogger } from "@/lib/metadata/metadataLoadCoordinator";
import type { MetadataScopeInput } from "@/lib/metadata/metadataLoadScope";
@@ -1604,7 +1605,8 @@ export const useConnectionStore = defineStore("connection", () => {
objects: options.objects.filter((object) => options.objectTypes.includes(normalizedObjectTreeKind(object.object_type))),
});
const refreshedGroup = grouped.find((group) => group.type === options.node.type);
return refreshedGroup?.children ?? [];
const children = refreshedGroup?.children ?? [];
return options.node.connectionId && getConfig(options.node.connectionId)?.db_type === "xugu" ? markXuguPackageNodesExpandable(children) : children;
}
function tableInfosToCompletionTables(tables: readonly TableInfo[], schema?: string): SqlCompletionTable[] {
@@ -1861,13 +1863,16 @@ export const useConnectionStore = defineStore("connection", () => {
);
const supplementalObjects = filterSimpleSidebarSupplementalObjects(objects);
if (supplementalObjects.length === 0) return;
const supplementalChildren = buildSimpleObjectTreeNodes({
let supplementalChildren = buildSimpleObjectTreeNodes({
nodeId: options.nodeId,
connectionId: options.connectionId,
database: options.database,
schema: options.effectiveSchema,
objects: supplementalObjects,
});
if (getConfig(options.connectionId)?.db_type === "xugu") {
supplementalChildren = markXuguPackageNodesExpandable(supplementalChildren);
}
if (supplementalChildren.length === 0) return;
if (isTreeLoadSearchChanged(searchFilter, options.loadOptions)) return;
const targetNode = treeNodeLoadTarget(options.load);
@@ -5613,6 +5618,57 @@ export const useConnectionStore = defineStore("connection", () => {
});
}
async function loadXuguPackageMembers(node: TreeNode, options?: LoadTreeOptions): Promise<void> {
if (node.type !== "package" || !node.connectionId || !node.database || getConfig(node.connectionId)?.db_type !== "xugu") return;
const connectionId = node.connectionId;
const database = node.database;
const schema = node.schema;
const packageName = node.objectName || node.label;
let load = beginTreeNodeLoad(node);
try {
await runTreeMetadataLoad(
{
kind: "xugu-package-members",
connectionId,
database,
schema,
nodeKind: node.type,
extra: { packageName },
},
async () => {
await ensureConnected(connectionId);
load = reclaimTreeNodeLoad(load, node);
const response = await completionAssistantSearch({
connection_id: connectionId,
database,
schema: schema ?? null,
object_kinds: ["routine"],
mask: "",
case_sensitive: true,
global_search: false,
max_results: 1000,
search_in_comments: false,
search_in_definitions: false,
parent_schema: schema ?? null,
parent_name: packageName,
match_mode: "prefix",
});
const targetNode = treeNodeLoadTarget(load);
if (!targetNode) return;
setChildren(targetNode, buildXuguPackageMemberNodes(targetNode, response.candidates));
targetNode.isExpanded = true;
},
options,
);
} catch (error) {
recordMetadataLoadError(connectionId, error, load);
throw error;
} finally {
finishTreeNodeLoad(load);
}
}
const ORACLE_SYSTEM_COMPLETION_SCHEMAS = new Set(["SYS", "SYSTEM", "SYSMAN", "DBSNMP", "OUTLN", "XDB", "MDSYS", "CTXSYS", "WMSYS"]);
const FILTERED_ROUTINE_COMPLETION_DATABASES = new Set<DatabaseType>(["mysql", "postgres", "sqlserver", "oracle"]);
@@ -7224,6 +7280,7 @@ export const useConnectionStore = defineStore("connection", () => {
loadTables,
loadTableForLocate,
loadObjectGroupChildren,
loadXuguPackageMembers,
loadMoreObjectGroupChildren,
loadAllObjectGroupChildren,
loadTableGroups,
+4
View File
@@ -890,6 +890,10 @@ export interface TreeNode {
tableName?: string;
objectName?: string;
signature?: string;
/** Owning package/type for nested routine members. */
parentName?: string;
parentSchema?: string;
parentType?: TreeNodeType;
tableType?: string;
comment?: string | null;
valid?: boolean | null;