feat(objects): show functions and procedures in sidebar tree

Add list_objects support for agent-driven databases (Dameng, Oracle, etc.)
and ExternalDriver plugin connections. SQL Server dbo schema now uses
listObjects to display grouped object nodes including routines.

- Add list_objects RPC to JDBC agent plugin
- Add extract_agent and ExternalDriver branches in list_objects_core
- Add get_object_source support for agent connections
- Click function/procedure nodes to view source via object browser
- Fix duplicate tree node IDs when same name exists as view and function
This commit is contained in:
t8y2
2026-05-15 13:02:19 +08:00
parent c8ba3dad69
commit c2da1f866c
7 changed files with 151 additions and 36 deletions
+19 -1
View File
@@ -322,6 +322,8 @@ function runRowClickAction() {
const action = treeNodeRowAction(node.type, canExpand.value);
if (action === "open-data") {
openData();
} else if (node.type === "procedure" || node.type === "function") {
void viewObjectSource();
} else if (node.type === "saved-sql-file") {
openSavedSqlFile();
} else if (action === "toggle") {
@@ -575,7 +577,23 @@ function buildDropObjectSql(): string {
}
function viewObjectSource() {
void openObjectBrowser();
const node = props.node;
if (!node.connectionId || !node.database) return;
const objectType = node.type === "procedure" ? "PROCEDURE" : "FUNCTION";
const schema = node.schema || node.database;
connectionStore
.ensureConnected(node.connectionId)
.then(() => {
connectionStore.activeConnectionId = node.connectionId!;
return api.getObjectSource(node.connectionId!, node.database!, schema, node.label, objectType as any);
})
.then((result) => {
const tabId = queryStore.createTab(node.connectionId!, node.database!, node.label);
queryStore.updateSql(tabId, result.source);
})
.catch((e: any) => {
toast(e?.message || String(e), 5000);
});
}
function requestDropObject() {
+9 -10
View File
@@ -1,4 +1,5 @@
import type { TableInfo, TreeNode } from "@/types/database";
import type { ObjectInfo, TreeNode } from "@/types/database";
import { buildGroupedObjectTreeNodes } from "@/lib/tableTree";
export const SQLSERVER_DEFAULT_SCHEMA = "dbo";
@@ -10,20 +11,18 @@ export function buildSqlServerDatabaseTreeNodes(
connectionId: string,
database: string,
schemas: string[],
defaultSchemaTables: TableInfo[],
defaultSchemaObjects: ObjectInfo[],
): TreeNode[] {
const databaseNodeId = `${connectionId}:${database}`;
const defaultSchema = schemas.find(isDefaultSchema) || SQLSERVER_DEFAULT_SCHEMA;
const defaultTableNodes = defaultSchemaTables.map((table) => ({
id: `${databaseNodeId}:${defaultSchema}:${table.name}`,
label: table.name,
type: (table.table_type === "VIEW" ? "view" : "table") as "view" | "table",
const defaultObjectNodes = buildGroupedObjectTreeNodes({
nodeId: databaseNodeId,
connectionId,
database,
schema: defaultSchema,
isExpanded: false,
children: [],
}));
objects: defaultSchemaObjects,
});
const schemaNodes = schemas
.filter((schema) => !isDefaultSchema(schema))
@@ -38,5 +37,5 @@ export function buildSqlServerDatabaseTreeNodes(
children: [],
}));
return [...defaultTableNodes, ...schemaNodes];
return [...defaultObjectNodes, ...schemaNodes];
}
+1 -1
View File
@@ -108,7 +108,7 @@ export function buildGroupedObjectTreeNodes({
objectCount: items.length,
isExpanded: false,
children: items.map((obj) => ({
id: `${nodeId}:${obj.name}`,
id: `${nodeId}:${def.key}:${obj.name}`,
label: obj.name,
type: def.childType,
connectionId,
+13 -6
View File
@@ -736,11 +736,11 @@ export const useConnectionStore = defineStore("connection", () => {
const cacheKey = schemaCacheKey(connectionId, database, "sqlserver-objects");
if (!options?.force && (await loadPersistedTreeChildren(node, cacheKey))) return;
const [schemas, defaultSchemaTables] = await Promise.all([
const [schemas, defaultSchemaObjects] = await Promise.all([
api.listSchemas(connectionId, database),
api.listTables(connectionId, database, SQLSERVER_DEFAULT_SCHEMA),
api.listObjects(connectionId, database, SQLSERVER_DEFAULT_SCHEMA),
]);
const children = buildSqlServerDatabaseTreeNodes(connectionId, database, schemas, defaultSchemaTables);
const children = buildSqlServerDatabaseTreeNodes(connectionId, database, schemas, defaultSchemaObjects);
setChildren(node, children);
await savePersistedTreeChildren(cacheKey, children);
node.isExpanded = true;
@@ -779,8 +779,15 @@ export const useConnectionStore = defineStore("connection", () => {
}
}
async function loadTableGroups(connectionId: string, database: string, table: string, schema?: string) {
const parentId = schema ? `${connectionId}:${database}:${schema}:${table}` : `${connectionId}:${database}:${table}`;
async function loadTableGroups(
connectionId: string,
database: string,
table: string,
schema?: string,
nodeId?: string,
) {
const parentId =
nodeId ?? (schema ? `${connectionId}:${database}:${schema}:${table}` : `${connectionId}:${database}:${table}`);
const node = findNode(treeNodes.value, parentId);
if (!node) return;
@@ -982,7 +989,7 @@ export const useConnectionStore = defineStore("connection", () => {
} else if (node.type === "schema" && node.connectionId && node.database && node.schema) {
await loadTables(node.connectionId, node.database, node.schema, options);
} else if ((node.type === "table" || node.type === "view") && node.connectionId && node.database) {
await loadTableGroups(node.connectionId, node.database, node.label, node.schema);
await loadTableGroups(node.connectionId, node.database, node.label, node.schema, node.id);
} else if (node.type === "group-columns" && node.connectionId && node.database && node.tableName) {
await loadColumns(node.connectionId, node.database, node.tableName, node.schema);
} else if (node.type === "group-indexes" && node.connectionId && node.database && node.tableName) {