feat(xugu): expose table child metadata

This commit is contained in:
Elias authored and GitHub committed 2026-07-24 16:47:13 +08:00
1 parent 6ddfa8bd24
commit 0362fef273
33 files changed
+1269 -33

No files matched your search

+3
View File
@@ -158,6 +158,9 @@ export const listDataTypes = forward("listDataTypes");
export const listIndexes = forward("listIndexes");
export const listForeignKeys = forward("listForeignKeys");
export const listTriggers = forward("listTriggers");
export const listConstraints = forward("listConstraints");
export const listPartitions = forward("listPartitions");
export const listSubpartitions = forward("listSubpartitions");
export const getTableDdl = forward("getTableDdl");
export const listFunctions = forward("listFunctions");
export const listSequences = forward("listSequences");
+15
View File
@@ -19,6 +19,9 @@ import type {
IndexInfo,
ForeignKeyInfo,
TriggerInfo,
ConstraintInfo,
PartitionInfo,
SubpartitionInfo,
ExtensionInfo,
FunctionInfo,
SequenceInfo,
@@ -685,6 +688,18 @@ export async function listTriggers(connectionId: string, database: string, schem
return get(`/api/schema/triggers?${qs({ connection_id: connectionId, database, schema, table, catalog })}`);
}
export async function listConstraints(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<ConstraintInfo[]> {
return get(`/api/schema/constraints?${qs({ connection_id: connectionId, database, schema, table, catalog })}`);
}
export async function listPartitions(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<PartitionInfo[]> {
return get(`/api/schema/partitions?${qs({ connection_id: connectionId, database, schema, table, catalog })}`);
}
export async function listSubpartitions(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<SubpartitionInfo[]> {
return get(`/api/schema/subpartitions?${qs({ connection_id: connectionId, database, schema, table, catalog })}`);
}
export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise<string> {
return get(`/api/schema/ddl?${qs({ connection_id: connectionId, database, schema, table, object_type: objectType, catalog })}`);
}
+15
View File
@@ -22,6 +22,9 @@ import type {
IndexInfo,
ForeignKeyInfo,
TriggerInfo,
ConstraintInfo,
PartitionInfo,
SubpartitionInfo,
FunctionInfo,
SequenceInfo,
RuleInfo,
@@ -1177,6 +1180,18 @@ export async function listTriggers(connectionId: string, database: string, schem
return invoke("list_triggers", { connectionId, database, schema, table, catalog });
}
export async function listConstraints(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<ConstraintInfo[]> {
return invoke("list_constraints", { connectionId, database, schema, table, catalog });
}
export async function listPartitions(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<PartitionInfo[]> {
return invoke("list_partitions", { connectionId, database, schema, table, catalog });
}
export async function listSubpartitions(connectionId: string, database: string, schema: string, table: string, catalog?: string): Promise<SubpartitionInfo[]> {
return invoke("list_subpartitions", { connectionId, database, schema, table, catalog });
}
export async function getTableDdl(connectionId: string, database: string, schema: string, table: string, objectType?: ObjectSourceKind, catalog?: string): Promise<string> {
return invoke("get_table_ddl", { connectionId, database, schema, table, objectType, catalog });
}
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { driverStoreFocusForInstallError } from "@/lib/connection/agentDriverInstallHint";
import { driverStoreFocusForInstallError, hasInstalledAgentVersion } from "@/lib/connection/agentDriverInstallHint";
describe("driverStoreFocusForInstallError", () => {
it("focuses the missing agent driver for driver-not-installed errors", () => {
@@ -38,3 +38,11 @@ describe("driverStoreFocusForInstallError", () => {
expect(driverStoreFocusForInstallError("No reachable ZooKeeper server within 2000ms: zk:2181", "zookeeper", undefined)).toBeNull();
});
});
describe("hasInstalledAgentVersion", () => {
it("requires an installed Agent at or above the requested release", () => {
expect(hasInstalledAgentVersion([{ db_type: "xugu", installed: true, installed_version: "0.1.23" }], "xugu", "0.1.23")).toBe(true);
expect(hasInstalledAgentVersion([{ db_type: "xugu", installed: true, installed_version: "0.1.22" }], "xugu", "0.1.23")).toBe(false);
expect(hasInstalledAgentVersion([{ db_type: "xugu", installed: true, installed_version: null }], "xugu", "0.1.23")).toBe(false);
});
});
@@ -4,9 +4,25 @@ import { supportsDriverManagement } from "@/lib/database/databaseCapabilities";
export interface AgentDriverInstallState {
db_type: string;
installed: boolean;
installed_version?: string | null;
update_available?: boolean;
}
/** Returns whether a locally installed native Agent meets a required release. */
export function hasInstalledAgentVersion(drivers: readonly AgentDriverInstallState[], driverKey: string, minimumVersion: string): boolean {
const installedVersion = drivers.find((driver) => driver.db_type === driverKey && driver.installed)?.installed_version;
if (!installedVersion) return false;
const parse = (version: string): number[] | null => {
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
return match ? match.slice(1).map(Number) : null;
};
const installed = parse(installedVersion);
const minimum = parse(minimumVersion);
if (!installed || !minimum) return false;
return installed[0] > minimum[0] || (installed[0] === minimum[0] && (installed[1] > minimum[1] || (installed[1] === minimum[1] && installed[2] >= minimum[2])));
}
export function agentDriverInstallKey(dbType: DatabaseType | undefined, driverProfile?: string): string | undefined {
if (dbType === "oracle") return "oracle";
if (dbType === "mongodb") return "mongodb";
@@ -12,7 +12,7 @@ const toggleLeafNodeTypes = new Set<TreeNodeType>(["redis-db", "mq-tenant", "etc
const objectBrowserNodeTypes = new Set<TreeNodeType>(["database", "schema", "object-browser"]);
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "trigger", "sequence", "package", "package-body", "type", "type-body"]);
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-partitions"]);
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-constraints", "group-partitions", "group-table-partitions", "group-table-subpartitions"]);
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-packages", "group-types"]);
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
@@ -53,6 +53,15 @@ export function getTreeNodeIconInfo(node: TreeNode): TreeNodeIconInfo | null {
return { icon: Zap, colorClass: "text-orange-400" };
case "trigger":
return { icon: Zap, colorClass: "text-orange-300" };
case "group-constraints":
case "constraint":
return { icon: Key, colorClass: "text-amber-500" };
case "group-table-partitions":
case "group-table-subpartitions":
return { icon: node.isExpanded ? FolderOpen : FolderClosed, colorClass: "text-green-400" };
case "partition":
case "subpartition":
return { icon: TableProperties, colorClass: "text-green-400" };
case "object-browser":
return { icon: TableProperties, colorClass: "text-primary" };
case "user-admin":