import type { DatabaseType } from "@/types/database"; export type SidebarObjectKind = "TABLE" | "VIEW" | "MATERIALIZED_VIEW" | "PROCEDURE" | "FUNCTION" | "TRIGGER" | "EVENT" | "SEQUENCE" | "SYNONYM" | "JOB" | "PACKAGE" | "PACKAGE_BODY" | "TYPE" | "TYPE_BODY"; export interface DatabaseObjectCapabilities { sidebarObjects: SidebarObjectKind[]; sourceReadable: SidebarObjectKind[]; executable: SidebarObjectKind[]; } const TABLE_VIEW_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW"]; const TABLE_VIEW_MV_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW"]; const ROUTINE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION"]; const MYSQL_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "EVENT"]; // PostgreSQL-family databases with a verified pg_type listing path. TYPE only // covers user-created types (enum/domain/composite/range/multirange/base); // relation auto-generated row types stay hidden. const POSTGRES_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "TYPE"]; const OPENGAUSS_A_OBJECTS: SidebarObjectKind[] = [...POSTGRES_OBJECTS, "PACKAGE", "PACKAGE_BODY"]; // KWDB is routed through the PostgreSQL pool but its pg_type catalog // compatibility is not verified yet, so it stays on the pre-TYPE object set. const POSTGRES_NO_TYPE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE"]; // Kingbase and Vastbase agents support the same user-defined type listing via // their own metadata query, but do not expose sequences. Kingbase additionally // has a verified schema-trigger path; keep Vastbase separate until its Agent // exposes the same parent-table-aware metadata contract. const KINGBASE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "TYPE"]; const VASTBASE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "TYPE"]; const POSTGRES_LIKE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION"]; const ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]; const OCEANBASE_ORACLE_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY"]; const DAMENG_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "MATERIALIZED_VIEW", "PROCEDURE", "FUNCTION", "SEQUENCE", "PACKAGE", "PACKAGE_BODY"]; const XUGU_OBJECTS: SidebarObjectKind[] = ["TABLE", "VIEW", "PROCEDURE", "FUNCTION", "TRIGGER", "SEQUENCE", "SYNONYM", "PACKAGE", "PACKAGE_BODY", "TYPE", "TYPE_BODY"]; const PACKAGE_MEMBER_EXPANSION_DATABASES = new Set(["oracle", "xugu"]); const DATABASE_TYPE_OBJECTS = new Map([ // postgres ["postgres", POSTGRES_OBJECTS], ["gaussdb", POSTGRES_OBJECTS], ["kwdb", POSTGRES_NO_TYPE_OBJECTS], ["opengauss", POSTGRES_OBJECTS], // postgres like ["kingbase", KINGBASE_OBJECTS], ["highgo", POSTGRES_LIKE_OBJECTS], ["uxdb", POSTGRES_LIKE_OBJECTS], ["vastbase", VASTBASE_OBJECTS], ["redshift", POSTGRES_LIKE_OBJECTS], // oracle ["oracle", ORACLE_OBJECTS], ["dameng", DAMENG_OBJECTS], ["oceanbase-oracle", OCEANBASE_ORACLE_OBJECTS], ["xugu", XUGU_OBJECTS], ["mysql", MYSQL_OBJECTS], // Explicit entry so schema-diff routine gating can opt in without relying on the // unknown-type ROUTINE_OBJECTS fallback. Keep the same object set the fallback // already used (no TRIGGER/SEQUENCE expansion in this change). ["sqlserver", ROUTINE_OBJECTS], // table and view ["sqlite", TABLE_VIEW_OBJECTS], ["rqlite", TABLE_VIEW_OBJECTS], ["turso", TABLE_VIEW_OBJECTS], ["cloudflare-d1", TABLE_VIEW_OBJECTS], ["duckdb", TABLE_VIEW_OBJECTS], ["clickhouse", TABLE_VIEW_OBJECTS], // Doris: backend listing path still uses the generic SHOW TABLES path (see // `list_tables_once` for `PoolKind::Mysql` in crates/dbx-core/src/schema/mod.rs) // and lacks a MV classifier. Keep Doris on TABLE_VIEW_OBJECTS until a // Doris-specific MV listing/classification lands, otherwise the UI advertises // MV support that the backend cannot route. ["doris", TABLE_VIEW_OBJECTS], ["starrocks", TABLE_VIEW_MV_OBJECTS], // Inceptor/Hive routines can be listed via JDBC plugin fallbacks (system.procedures_v/functions_v). ["hive", ROUTINE_OBJECTS], // ArgoDB (Transwarp) shares the Hive agent; its catalog views // (system.procedures_v / system.functions_v) expose routines natively. ["argo", ROUTINE_OBJECTS], ["kyuubi", TABLE_VIEW_OBJECTS], ["impala", TABLE_VIEW_OBJECTS], ["spark", TABLE_VIEW_OBJECTS], ["trino", TABLE_VIEW_OBJECTS], ["prestosql", TABLE_VIEW_OBJECTS], ["cassandra", TABLE_VIEW_OBJECTS], ["bigquery", TABLE_VIEW_OBJECTS], // Cloud Spanner has no triggers, routines, sequences, or synonyms; without an // explicit entry the sidebar would fall back to ROUTINE_OBJECTS. ["spanner", TABLE_VIEW_OBJECTS], ["kylin", TABLE_VIEW_OBJECTS], ["ignite", TABLE_VIEW_OBJECTS], ["ignite3", TABLE_VIEW_OBJECTS], ["tdengine", TABLE_VIEW_OBJECTS], ["iotdb", TABLE_VIEW_OBJECTS], ["neo4j", TABLE_VIEW_OBJECTS], // others ["influxdb", ["TABLE"]], ["influxdb3", ["TABLE"]], ["victoriametrics", ["TABLE"]], ["hbase", ["TABLE"]], ["questdb", ["TABLE", "VIEW", "MATERIALIZED_VIEW"]], ["manticoresearch", ["TABLE", "FUNCTION"]], ["databend", ["TABLE", "VIEW", "PROCEDURE"]], ]); /** * Whether a kind is readable as object source for the given connection type. * TYPE/TYPE_BODY only have a real source implementation on Xugu; PostgreSQL- * family databases list types without a CREATE TYPE getter this cycle. */ function isSourceReadableObjectKind(kind: SidebarObjectKind, dbType?: DatabaseType): boolean { if (kind === "TABLE") return false; if (kind === "TYPE" || kind === "TYPE_BODY") return supportsTypeObjectSource(dbType); return true; } export function databaseObjectCapabilities(dbType?: DatabaseType, compatibilityMode?: string): DatabaseObjectCapabilities { const sidebarObjects = sidebarObjectKindsForDatabase(dbType, compatibilityMode); return { sidebarObjects, sourceReadable: sidebarObjects.filter((kind) => isSourceReadableObjectKind(kind, dbType)), executable: sidebarObjects.filter((kind) => kind === "PROCEDURE"), }; } const SCHEMA_DIFF_ROUTINE_KINDS = ["PROCEDURE", "FUNCTION"] as const; /** Same-dialect families with verified list_objects + get_object_source (or PG catalog) paths. */ const SCHEMA_DIFF_ROUTINE_FAMILY = new Map([ ["postgres", "postgres"], ["opengauss", "postgres"], ["gaussdb", "postgres"], ["kwdb", "postgres"], ["kingbase", "postgres"], ["highgo", "postgres"], ["uxdb", "postgres"], ["vastbase", "postgres"], ["redshift", "postgres"], ["mysql", "mysql"], ["sqlserver", "sqlserver"], ]); /** * Schema Diff routine compare is limited to an allowlist of same-dialect families. * Sidebar may list routines for more engines (oracle/hive/…); those stay out of * schema-diff until a verified compare path lands. Cross-family pairs never match. */ export function supportsSchemaDiffRoutines(dbType?: DatabaseType): boolean { if (!dbType || !SCHEMA_DIFF_ROUTINE_FAMILY.has(dbType)) return false; const { sidebarObjects, sourceReadable } = databaseObjectCapabilities(dbType); return SCHEMA_DIFF_ROUTINE_KINDS.some((kind) => sidebarObjects.includes(kind) && sourceReadable.includes(kind)); } export function schemaDiffRoutineObjectTypes(dbType?: DatabaseType): Array<"PROCEDURE" | "FUNCTION"> { if (!supportsSchemaDiffRoutines(dbType)) return []; const { sidebarObjects, sourceReadable } = databaseObjectCapabilities(dbType); return SCHEMA_DIFF_ROUTINE_KINDS.filter((kind) => sidebarObjects.includes(kind) && sourceReadable.includes(kind)); } export function schemaDiffRoutineObjectTypesIntersection(sourceDbType?: DatabaseType, targetDbType?: DatabaseType): Array<"PROCEDURE" | "FUNCTION"> { if (!sourceDbType || !targetDbType) return []; const sourceFamily = SCHEMA_DIFF_ROUTINE_FAMILY.get(sourceDbType); const targetFamily = SCHEMA_DIFF_ROUTINE_FAMILY.get(targetDbType); if (!sourceFamily || sourceFamily !== targetFamily) return []; const sourceTypes = new Set(schemaDiffRoutineObjectTypes(sourceDbType)); return schemaDiffRoutineObjectTypes(targetDbType).filter((kind) => sourceTypes.has(kind)); } export function sidebarObjectKindsForDatabase(dbType?: DatabaseType, compatibilityMode?: string): SidebarObjectKind[] { if (!dbType) return [...TABLE_VIEW_OBJECTS]; if (dbType === "opengauss" && compatibilityMode?.trim().toUpperCase() === "A") return [...OPENGAUSS_A_OBJECTS]; return DATABASE_TYPE_OBJECTS.get(dbType) ?? [...ROUTINE_OBJECTS]; } /** * Whether a connection's TYPE tree nodes may be opened as object source. * * Xugu has a real TYPE/TYPE_BODY source implementation. PostgreSQL-family * databases only list user-defined types this cycle; their CREATE TYPE DDL has * no unified catalog getter, so opening source would error. Callers must gate * the source action (single/double click, context menu, shortcuts) on this * before dispatching getObjectSource. */ export function supportsTypeObjectSource(dbType?: DatabaseType): boolean { return dbType === "xugu"; } export type CustomTypeCapabilities = { details: boolean; members: boolean; ddl: boolean; }; const VERIFIED_CUSTOM_TYPE_DATABASES = new Set(["postgres", "opengauss", "gaussdb", "kingbase", "vastbase"]); /** * Whether a connection may open read-only custom type details (phase 2). * Kept separate from the listing capability so a future per-kind DDL toggle * can be introduced without touching the object-list sets. */ export function customTypeCapabilities(dbType?: DatabaseType): CustomTypeCapabilities { const supported = !!dbType && VERIFIED_CUSTOM_TYPE_DATABASES.has(dbType); return { details: supported, members: supported, ddl: supported }; } export function supportsPackageMemberExpansion(dbType?: DatabaseType, compatibilityMode?: string): boolean { return !!dbType && (PACKAGE_MEMBER_EXPANSION_DATABASES.has(dbType) || (dbType === "opengauss" && compatibilityMode?.trim().toUpperCase() === "A")); } export function normalizeSidebarObjectKind(type: string): SidebarObjectKind { const value = type.toUpperCase(); const normalized = value.replace(/[\s-]+/g, "_"); if (normalized.includes("PACKAGE_BODY")) return "PACKAGE_BODY"; if (normalized.includes("TYPE_BODY")) return "TYPE_BODY"; if (normalized.includes("PACKAGE")) return "PACKAGE"; if (normalized.includes("TRIGGER")) return "TRIGGER"; if (normalized.includes("EVENT")) return "EVENT"; if (normalized.includes("TYPE")) return "TYPE"; if (normalized.includes("MATERIALIZED_VIEW")) return "MATERIALIZED_VIEW"; if (value.includes("VIEW")) return "VIEW"; if (value.includes("SEQ")) return "SEQUENCE"; if (value.includes("SYNONYM")) return "SYNONYM"; if (value.includes("JOB")) return "JOB"; if (value.includes("PROC")) return "PROCEDURE"; if (value.includes("FUNC")) return "FUNCTION"; return "TABLE"; }