From 5832e1b194287e7caa9ca4bd54618aa420b919ea Mon Sep 17 00:00:00 2001 From: t8y2 <1156263951@qq.com> Date: Wed, 13 May 2026 22:14:33 +0800 Subject: [PATCH] refactor(frontend): centralize database feature capabilities --- src/App.vue | 4 +- .../connection/ConnectionDialog.vue | 4 +- .../diagram/SchemaDiagramDialog.vue | 6 +- .../search/DatabaseSearchDialog.vue | 6 +- .../transfer/DataTransferDialog.vue | 4 +- src/lib/databaseCapabilities.ts | 181 +----------------- src/lib/databaseCapabilitySets.ts | 166 ++++++++++++++++ src/lib/databaseFeatureSupport.ts | 81 ++++++++ src/lib/databaseTableDataCapabilities.ts | 74 +++++++ src/lib/tableEditing.ts | 18 +- src/stores/connectionStore.ts | 4 +- tests/databaseCapabilities.test.ts | 91 ++++++++- 12 files changed, 435 insertions(+), 204 deletions(-) create mode 100644 src/lib/databaseCapabilitySets.ts create mode 100644 src/lib/databaseFeatureSupport.ts create mode 100644 src/lib/databaseTableDataCapabilities.ts diff --git a/src/App.vue b/src/App.vue index 670f140feb..3f45eba7d9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -36,7 +36,7 @@ import { resolveExecutableSql } from "@/lib/sqlExecutionTarget"; import { isTauriRuntime } from "@/lib/tauriRuntime"; import { isCloseTabShortcut, isExecuteSqlShortcut, isFocusSearchShortcut } from "@/lib/keyboardShortcuts"; import { isPreviewTab } from "@/lib/tabPresentation"; -import { SQL_FILE_UNSUPPORTED_TYPES } from "@/lib/databaseCapabilities"; +import { supportsSqlFileExecution } from "@/lib/databaseCapabilities"; import { classifyAiSqlExecution } from "@/lib/aiSqlExecutionPolicy"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; @@ -145,7 +145,7 @@ const { setupTauriListeners, cleanupTauriListeners } = useTauriEvents({ openTabl const appVersion = ref(""); const isClassicLayout = computed(() => settingsStore.editorSettings.appLayout === "classic"); const hasSqlFileConnections = computed(() => - connectionStore.connections.some((c) => !SQL_FILE_UNSUPPORTED_TYPES.has(c.db_type)), + connectionStore.connections.some((c) => supportsSqlFileExecution(c.db_type)), ); const connectionStats = computed(() => ({ total: connectionStore.connections.length, diff --git a/src/components/connection/ConnectionDialog.vue b/src/components/connection/ConnectionDialog.vue index 6e98bffc1e..1f568d517f 100644 --- a/src/components/connection/ConnectionDialog.vue +++ b/src/components/connection/ConnectionDialog.vue @@ -17,7 +17,7 @@ import * as api from "@/lib/api"; import { isTauriRuntime } from "@/lib/tauriRuntime"; import { applyParsedConnectionUrl, parseConnectionUrl } from "@/lib/connectionUrl"; import { connectionUrlPlaceholder as getUrlPlaceholder } from "@/lib/connectionPresentation"; -import { AGENT_DRIVER_TYPES } from "@/lib/databaseCapabilities"; +import { supportsDriverManagement } from "@/lib/databaseCapabilities"; import { ArrowLeft, ChevronRight, Copy, ExternalLink, FolderOpen, Grid3X3, Link2, List, Search } from "lucide-vue-next"; type DbOption = { value: string; label: string }; @@ -1130,7 +1130,7 @@ function openExternalUrl(url: string) { -
+

需要在顶部导航栏「驱动管理」中安装对应的驱动才能连接。 diff --git a/src/components/diagram/SchemaDiagramDialog.vue b/src/components/diagram/SchemaDiagramDialog.vue index 4354060155..f4343ba67b 100644 --- a/src/components/diagram/SchemaDiagramDialog.vue +++ b/src/components/diagram/SchemaDiagramDialog.vue @@ -9,7 +9,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { useConnectionStore } from "@/stores/connectionStore"; import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import * as api from "@/lib/api"; -import { DIAGRAM_SQL_TYPES, SCHEMA_AWARE_TYPES } from "@/lib/databaseCapabilities"; +import { DIAGRAM_SQL_TYPES, isSchemaAware as isSchemaAwareDatabase } from "@/lib/databaseCapabilities"; import { buildDiagramRelationships, filterDiagramTables, @@ -91,9 +91,7 @@ const sqlConnections = computed(() => const selectedConnection = computed(() => (connectionId.value ? store.getConfig(connectionId.value) : undefined)); -const isSchemaAware = computed( - () => !!selectedConnection.value && SCHEMA_AWARE_TYPES.has(selectedConnection.value.db_type), -); +const isSchemaAware = computed(() => isSchemaAwareDatabase(selectedConnection.value?.db_type)); const allRelationships = computed(() => buildDiagramRelationships(tables.value)); diff --git a/src/components/search/DatabaseSearchDialog.vue b/src/components/search/DatabaseSearchDialog.vue index 4155ca7220..53fe634496 100644 --- a/src/components/search/DatabaseSearchDialog.vue +++ b/src/components/search/DatabaseSearchDialog.vue @@ -12,7 +12,7 @@ import { useConnectionStore } from "@/stores/connectionStore"; import * as api from "@/lib/api"; import { buildDatabaseSearchSql, buildSearchResultWhere, findMatchedSearchColumns } from "@/lib/databaseSearch"; import type { DatabaseType, TableInfo } from "@/types/database"; -import { SCHEMA_AWARE_TYPES } from "@/lib/databaseCapabilities"; +import { isSchemaAware } from "@/lib/databaseCapabilities"; const props = defineProps<{ open: boolean; @@ -116,10 +116,6 @@ function resetSearchState() { currentExecutionId.value = ""; } -function isSchemaAware(databaseType?: DatabaseType): boolean { - return !!databaseType && SCHEMA_AWARE_TYPES.has(databaseType); -} - function isSearchableTable(table: TableInfo): boolean { const type = table.table_type.toUpperCase(); return type.includes("TABLE") && !type.includes("VIEW"); diff --git a/src/components/transfer/DataTransferDialog.vue b/src/components/transfer/DataTransferDialog.vue index f994af7099..f66cbec21e 100644 --- a/src/components/transfer/DataTransferDialog.vue +++ b/src/components/transfer/DataTransferDialog.vue @@ -12,7 +12,7 @@ import DatabaseIcon from "@/components/icons/DatabaseIcon.vue"; import * as api from "@/lib/api"; import type { TransferProgress, TransferMode } from "@/lib/api"; import type { DatabaseType } from "@/types/database"; -import { TRANSFER_SQL_TYPES, isSchemaAware } from "@/lib/databaseCapabilities"; +import { isSchemaAware, supportsTransfer } from "@/lib/databaseCapabilities"; import { nextTransferTerminalState } from "@/lib/transferProgressState"; import { ArrowRightLeft, Check, X, Loader2, Square, CheckSquare } from "lucide-vue-next"; @@ -26,7 +26,7 @@ const props = defineProps<{ const store = useConnectionStore(); -const sqlConnections = computed(() => store.connections.filter((c) => TRANSFER_SQL_TYPES.has(c.db_type))); +const sqlConnections = computed(() => store.connections.filter((c) => supportsTransfer(c.db_type))); // Source state const sourceConnectionId = ref(""); diff --git a/src/lib/databaseCapabilities.ts b/src/lib/databaseCapabilities.ts index 5c06d09349..9b4e979dae 100644 --- a/src/lib/databaseCapabilities.ts +++ b/src/lib/databaseCapabilities.ts @@ -1,178 +1,3 @@ -import type { DatabaseType } from "@/types/database"; - -export const SCHEMA_AWARE_TYPES = new Set([ - "postgres", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", - "kingbase", - "vastbase", - "jdbc", - "h2", - "snowflake", - "trino", - "db2", - "informix", -]); - -export const SQL_FILE_UNSUPPORTED_TYPES = new Set(["redis", "mongodb", "elasticsearch"]); - -export const DIAGRAM_SUPPORTED_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", - "kingbase", - "vastbase", - "goldendb", - "h2", - "db2", -]); - -export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "duckdb", - "clickhouse", - "dameng", - "gaussdb", - "kingbase", - "vastbase", - "goldendb", - "h2", - "snowflake", - "trino", - "hive", - "db2", - "informix", - "neo4j", - "cassandra", - "bigquery", - "kylin", - "sundb", -]); - -export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "duckdb", - "clickhouse", - "sqlserver", - "oracle", - "doris", - "starrocks", - "redshift", - "dameng", - "gaussdb", - "kingbase", - "vastbase", - "goldendb", -]); - -export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); - -export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ - "mysql", - "postgres", - "sqlserver", - "clickhouse", - "oracle", - "dameng", - "gaussdb", - "doris", - "starrocks", - "redshift", -]); - -export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", -]); - -export const SINGLE_DATABASE_TYPES = new Set(["oracle", "dameng"]); - -export const FETCH_FIRST_TYPES = new Set(["oracle", "dameng"]); - -export const TREE_SCHEMA_TYPES = new Set([ - "postgres", - "redshift", - "sqlserver", - "gaussdb", - "jdbc", - "trino", - "h2", -]); - -export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb"]); - -export const AGENT_DRIVER_TYPES = new Set([ - "dameng", - "kingbase", - "vastbase", - "goldendb", - "oracle", - "h2", - "snowflake", - "trino", - "hive", - "db2", - "informix", - "neo4j", - "cassandra", - "bigquery", - "kylin", - "sundb", - "gaussdb", -]); - -export const TRANSFER_SQL_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "clickhouse", - "duckdb", - "dameng", - "gaussdb", -]); - -export const DIAGRAM_SQL_TYPES = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", -]); - -export function isSchemaAware(dbType?: DatabaseType): boolean { - return !!dbType && SCHEMA_AWARE_TYPES.has(dbType); -} - -export function isSingleDatabase(dbType?: DatabaseType): boolean { - return !!dbType && SINGLE_DATABASE_TYPES.has(dbType); -} - -export function usesFetchFirst(dbType?: DatabaseType): boolean { - return !!dbType && FETCH_FIRST_TYPES.has(dbType); -} +export * from "./databaseCapabilitySets"; +export * from "./databaseFeatureSupport"; +export * from "./databaseTableDataCapabilities"; diff --git a/src/lib/databaseCapabilitySets.ts b/src/lib/databaseCapabilitySets.ts new file mode 100644 index 0000000000..3e77c5749b --- /dev/null +++ b/src/lib/databaseCapabilitySets.ts @@ -0,0 +1,166 @@ +import type { DatabaseType } from "@/types/database"; + +export const SCHEMA_AWARE_TYPES = new Set([ + "postgres", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", + "kingbase", + "vastbase", + "jdbc", + "h2", + "snowflake", + "trino", + "db2", + "informix", +]); + +export const SQL_FILE_UNSUPPORTED_TYPES = new Set(["redis", "mongodb", "elasticsearch"]); + +export const DIAGRAM_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", + "kingbase", + "vastbase", + "goldendb", + "h2", + "db2", +]); + +export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "duckdb", + "clickhouse", + "dameng", + "gaussdb", + "kingbase", + "vastbase", + "goldendb", + "h2", + "snowflake", + "trino", + "hive", + "db2", + "informix", + "neo4j", + "cassandra", + "bigquery", + "kylin", + "sundb", +]); + +export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "duckdb", + "clickhouse", + "sqlserver", + "oracle", + "doris", + "starrocks", + "redshift", + "dameng", + "gaussdb", + "kingbase", + "vastbase", + "goldendb", +]); + +export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); + +export const CREATE_DATABASE_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlserver", + "clickhouse", + "oracle", + "dameng", + "gaussdb", + "doris", + "starrocks", + "redshift", +]); + +export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", +]); + +export const SINGLE_DATABASE_TYPES = new Set(["oracle", "dameng"]); + +export const FETCH_FIRST_TYPES = new Set(["oracle", "dameng"]); + +export const TREE_SCHEMA_TYPES = new Set([ + "postgres", + "redshift", + "sqlserver", + "gaussdb", + "jdbc", + "trino", + "h2", +]); + +export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "gaussdb"]); + +export const AGENT_DRIVER_TYPES = new Set([ + "dameng", + "kingbase", + "vastbase", + "goldendb", + "oracle", + "h2", + "snowflake", + "trino", + "hive", + "db2", + "informix", + "neo4j", + "cassandra", + "bigquery", + "kylin", + "sundb", + "gaussdb", +]); + +export const TRANSFER_SQL_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "clickhouse", + "duckdb", + "dameng", + "gaussdb", +]); + +export const DIAGRAM_SQL_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", +]); diff --git a/src/lib/databaseFeatureSupport.ts b/src/lib/databaseFeatureSupport.ts new file mode 100644 index 0000000000..a49557bd69 --- /dev/null +++ b/src/lib/databaseFeatureSupport.ts @@ -0,0 +1,81 @@ +import type { DatabaseType } from "@/types/database"; +import { + AGENT_DRIVER_TYPES, + CREATE_DATABASE_SUPPORTED_TYPES, + DATABASE_SEARCH_SUPPORTED_TYPES, + DIAGRAM_SUPPORTED_TYPES, + FETCH_FIRST_TYPES, + FIELD_LINEAGE_SUPPORTED_TYPES, + PG_LIKE_STRUCTURE_TYPES, + SCHEMA_AWARE_TYPES, + SINGLE_DATABASE_TYPES, + SQL_FILE_UNSUPPORTED_TYPES, + TABLE_IMPORT_SUPPORTED_TYPES, + TABLE_STRUCTURE_SUPPORTED_TYPES, + TRANSFER_SQL_TYPES, + TREE_SCHEMA_TYPES, +} from "./databaseCapabilitySets"; + +export function isSchemaAware(dbType?: DatabaseType): boolean { + return !!dbType && SCHEMA_AWARE_TYPES.has(dbType); +} + +export function usesTreeSchemaMode(dbType?: DatabaseType): boolean { + return !!dbType && TREE_SCHEMA_TYPES.has(dbType); +} + +export function isSingleDatabase(dbType?: DatabaseType): boolean { + return !!dbType && SINGLE_DATABASE_TYPES.has(dbType); +} + +export function usesFetchFirst(dbType?: DatabaseType): boolean { + return !!dbType && FETCH_FIRST_TYPES.has(dbType); +} + +export function supportsSqlFileExecution(dbType?: DatabaseType): boolean { + return !!dbType && !SQL_FILE_UNSUPPORTED_TYPES.has(dbType); +} + +export function supportsSchemaDiagram(dbType?: DatabaseType): boolean { + return !!dbType && DIAGRAM_SUPPORTED_TYPES.has(dbType); +} + +export function supportsDatabaseSearch(dbType?: DatabaseType): boolean { + return !!dbType && DATABASE_SEARCH_SUPPORTED_TYPES.has(dbType); +} + +export function supportsTableImport(dbType?: DatabaseType): boolean { + return !!dbType && TABLE_IMPORT_SUPPORTED_TYPES.has(dbType); +} + +export function supportsTableStructureEditing(dbType?: DatabaseType): boolean { + return !!dbType && TABLE_STRUCTURE_SUPPORTED_TYPES.has(dbType); +} + +export function supportsDatabaseCreation(dbType?: DatabaseType): boolean { + return !!dbType && CREATE_DATABASE_SUPPORTED_TYPES.has(dbType); +} + +export function supportsFieldLineage(dbType?: DatabaseType): boolean { + return !!dbType && FIELD_LINEAGE_SUPPORTED_TYPES.has(dbType); +} + +export function supportsTransfer(dbType?: DatabaseType): boolean { + return !!dbType && TRANSFER_SQL_TYPES.has(dbType); +} + +export function supportsDriverManagement(dbType?: DatabaseType): boolean { + return !!dbType && AGENT_DRIVER_TYPES.has(dbType); +} + +export function supportsObjectBrowser(dbType?: DatabaseType): boolean { + return !!dbType && !["redis", "mongodb", "elasticsearch"].includes(dbType); +} + +export function supportsTableTruncate(dbType?: DatabaseType): boolean { + return !!dbType && dbType !== "sqlite" && dbType !== "duckdb"; +} + +export function usesPostgresLikeStructureCopy(dbType?: DatabaseType): boolean { + return !!dbType && PG_LIKE_STRUCTURE_TYPES.has(dbType); +} diff --git a/src/lib/databaseTableDataCapabilities.ts b/src/lib/databaseTableDataCapabilities.ts new file mode 100644 index 0000000000..ec22ec2aca --- /dev/null +++ b/src/lib/databaseTableDataCapabilities.ts @@ -0,0 +1,74 @@ +import type { DatabaseType } from "@/types/database"; +import { isSchemaAware, usesTreeSchemaMode } from "./databaseFeatureSupport"; + +export type SyntheticEditKey = "oracle-rowid" | "neo4j-element-id"; + +export interface TableDataCapability { + insert: boolean; + updateRequiresPrimaryKey: boolean; + deleteRequiresPrimaryKey: boolean; + requiresTransactionalTableForExistingRows: boolean; + transaction: boolean; +} + +export interface DatabaseCapability { + schemaAware: boolean; + treeSchemaMode: boolean; + tableData: TableDataCapability; + syntheticKey?: SyntheticEditKey; +} + +const DEFAULT_TABLE_DATA_CAPABILITY: TableDataCapability = { + insert: false, + updateRequiresPrimaryKey: true, + deleteRequiresPrimaryKey: true, + requiresTransactionalTableForExistingRows: false, + transaction: true, +}; + +const DEFAULT_CAPABILITY: DatabaseCapability = { + schemaAware: false, + treeSchemaMode: false, + tableData: DEFAULT_TABLE_DATA_CAPABILITY, +}; + +const DATABASE_CAPABILITY_OVERRIDES: Partial>> = { + hive: { + tableData: { + insert: true, + updateRequiresPrimaryKey: false, + deleteRequiresPrimaryKey: false, + requiresTransactionalTableForExistingRows: true, + transaction: false, + }, + }, + neo4j: { + syntheticKey: "neo4j-element-id", + }, + oracle: { + syntheticKey: "oracle-rowid", + }, + trino: { + tableData: { + insert: true, + updateRequiresPrimaryKey: true, + deleteRequiresPrimaryKey: true, + requiresTransactionalTableForExistingRows: false, + transaction: false, + }, + }, +}; + +export function getDatabaseCapability(dbType?: DatabaseType): DatabaseCapability { + const override = dbType ? DATABASE_CAPABILITY_OVERRIDES[dbType] : undefined; + return { + ...DEFAULT_CAPABILITY, + ...override, + schemaAware: isSchemaAware(dbType), + treeSchemaMode: usesTreeSchemaMode(dbType), + tableData: { + ...DEFAULT_TABLE_DATA_CAPABILITY, + ...override?.tableData, + }, + }; +} diff --git a/src/lib/tableEditing.ts b/src/lib/tableEditing.ts index 7ea66a8884..6efd3425eb 100644 --- a/src/lib/tableEditing.ts +++ b/src/lib/tableEditing.ts @@ -1,32 +1,34 @@ import type { ColumnInfo, DatabaseType } from "@/types/database"; +import { getDatabaseCapability } from "./databaseCapabilities"; export const DBX_ROWID_COLUMN = "__DBX_ROWID"; export const DBX_NEO4J_ELEMENT_ID_COLUMN = "__DBX_ELEMENT_ID"; export function editablePrimaryKeys(databaseType: DatabaseType | undefined, columns: ColumnInfo[]): string[] { const primaryKeys = columns.filter((column) => column.is_primary_key).map((column) => column.name); - if (databaseType === "oracle" && primaryKeys.length === 0) return [DBX_ROWID_COLUMN]; - if (databaseType === "neo4j" && primaryKeys.length === 0) return [DBX_NEO4J_ELEMENT_ID_COLUMN]; + const syntheticKey = getDatabaseCapability(databaseType).syntheticKey; + if (syntheticKey === "oracle-rowid" && primaryKeys.length === 0) return [DBX_ROWID_COLUMN]; + if (syntheticKey === "neo4j-element-id" && primaryKeys.length === 0) return [DBX_NEO4J_ELEMENT_ID_COLUMN]; return primaryKeys; } export function isTableDataEditable(databaseType: DatabaseType | undefined, primaryKeys: string[]): boolean { - if (databaseType === "hive") return true; - if (databaseType === "trino") return true; + if (getDatabaseCapability(databaseType).tableData.insert) return true; return primaryKeys.length > 0; } export function supportsDataGridTransaction(databaseType: DatabaseType | undefined): boolean { - return databaseType !== "hive" && databaseType !== "trino"; + return getDatabaseCapability(databaseType).tableData.transaction; } export function canEditExistingTableRows( databaseType: DatabaseType | undefined, hiveTableTransactional?: boolean, - primaryKeys: string[] = [], + primaryKeys?: string[], ): boolean { - if (databaseType === "hive") return hiveTableTransactional === true; - if (databaseType === "trino") return primaryKeys.length > 0; + const tableData = getDatabaseCapability(databaseType).tableData; + if (tableData.requiresTransactionalTableForExistingRows && hiveTableTransactional !== true) return false; + if (tableData.updateRequiresPrimaryKey && primaryKeys && primaryKeys.length === 0) return false; return true; } diff --git a/src/stores/connectionStore.ts b/src/stores/connectionStore.ts index dd7116c50d..bfece434b4 100644 --- a/src/stores/connectionStore.ts +++ b/src/stores/connectionStore.ts @@ -20,7 +20,7 @@ import { import type { SqlCompletionColumn, SqlCompletionTable } from "@/lib/sqlCompletion"; import * as api from "@/lib/api"; import { isTauriRuntime } from "@/lib/tauriRuntime"; -import { isSchemaAware, TREE_SCHEMA_TYPES } from "@/lib/databaseCapabilities"; +import { isSchemaAware, usesTreeSchemaMode } from "@/lib/databaseCapabilities"; import { buildDatabaseTreeNodes } from "@/lib/databaseTree"; import { buildSqlServerDatabaseTreeNodes, SQLSERVER_DEFAULT_SCHEMA } from "@/lib/sqlServerTree"; import { @@ -932,7 +932,7 @@ export const useConnectionStore = defineStore("connection", () => { const config = getConfig(node.connectionId); if (config?.db_type === "sqlserver") { await loadSqlServerDatabaseObjects(node.connectionId, node.database, options); - } else if (config?.db_type && TREE_SCHEMA_TYPES.has(config.db_type)) { + } else if (usesTreeSchemaMode(config?.db_type)) { await loadSchemas(node.connectionId, node.database, options); } else { await loadTables(node.connectionId, node.database, undefined, options); diff --git a/tests/databaseCapabilities.test.ts b/tests/databaseCapabilities.test.ts index 46d06bda2b..c683377461 100644 --- a/tests/databaseCapabilities.test.ts +++ b/tests/databaseCapabilities.test.ts @@ -1,11 +1,100 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { SCHEMA_AWARE_TYPES, TREE_SCHEMA_TYPES } from "../src/lib/databaseCapabilities.ts"; +import { + SCHEMA_AWARE_TYPES, + TREE_SCHEMA_TYPES, + getDatabaseCapability, + supportsDatabaseCreation, + supportsDatabaseSearch, + supportsDriverManagement, + supportsFieldLineage, + supportsObjectBrowser, + supportsSchemaDiagram, + supportsSqlFileExecution, + supportsTableImport, + supportsTableTruncate, + supportsTableStructureEditing, + supportsTransfer, + usesPostgresLikeStructureCopy, + usesTreeSchemaMode, +} from "../src/lib/databaseCapabilities.ts"; test("treats Trino catalogs as schema tree roots", () => { assert.equal(TREE_SCHEMA_TYPES.has("trino"), true); }); +test("describes schema tree mode through the capability helper", () => { + assert.equal(usesTreeSchemaMode("trino"), true); + assert.equal(usesTreeSchemaMode("h2"), true); + assert.equal(usesTreeSchemaMode("mysql"), false); + assert.equal(usesTreeSchemaMode(undefined), false); +}); + test("treats Trino tables as schema-qualified SQL targets", () => { assert.equal(SCHEMA_AWARE_TYPES.has("trino"), true); }); + +test("describes table editing capabilities for special database engines", () => { + assert.deepEqual(getDatabaseCapability("hive").tableData, { + insert: true, + updateRequiresPrimaryKey: false, + deleteRequiresPrimaryKey: false, + requiresTransactionalTableForExistingRows: true, + transaction: false, + }); + + assert.deepEqual(getDatabaseCapability("trino").tableData, { + insert: true, + updateRequiresPrimaryKey: true, + deleteRequiresPrimaryKey: true, + requiresTransactionalTableForExistingRows: false, + transaction: false, + }); + + assert.equal(getDatabaseCapability("oracle").syntheticKey, "oracle-rowid"); + assert.equal(getDatabaseCapability("neo4j").syntheticKey, "neo4j-element-id"); +}); + +test("uses conservative table editing defaults for unknown or keyless relational engines", () => { + assert.deepEqual(getDatabaseCapability("postgres").tableData, { + insert: false, + updateRequiresPrimaryKey: true, + deleteRequiresPrimaryKey: true, + requiresTransactionalTableForExistingRows: false, + transaction: true, + }); + assert.deepEqual(getDatabaseCapability(undefined).tableData, { + insert: false, + updateRequiresPrimaryKey: true, + deleteRequiresPrimaryKey: true, + requiresTransactionalTableForExistingRows: false, + transaction: true, + }); +}); + +test("describes feature support through capability helpers", () => { + assert.equal(supportsSqlFileExecution("mysql"), true); + assert.equal(supportsSqlFileExecution("redis"), false); + assert.equal(supportsSchemaDiagram("oracle"), true); + assert.equal(supportsSchemaDiagram("trino"), false); + assert.equal(supportsDatabaseSearch("neo4j"), true); + assert.equal(supportsDatabaseSearch("redis"), false); + assert.equal(supportsTableImport("duckdb"), true); + assert.equal(supportsTableImport("hive"), false); + assert.equal(supportsTableStructureEditing("postgres"), true); + assert.equal(supportsTableStructureEditing("oracle"), false); + assert.equal(supportsDatabaseCreation("clickhouse"), true); + assert.equal(supportsDatabaseCreation("sqlite"), false); + assert.equal(supportsFieldLineage("gaussdb"), true); + assert.equal(supportsFieldLineage("trino"), false); + assert.equal(supportsTransfer("duckdb"), true); + assert.equal(supportsTransfer("hive"), false); + assert.equal(supportsDriverManagement("oracle"), true); + assert.equal(supportsDriverManagement("mysql"), false); + assert.equal(usesPostgresLikeStructureCopy("gaussdb"), true); + assert.equal(usesPostgresLikeStructureCopy("mysql"), false); + assert.equal(supportsObjectBrowser("mysql"), true); + assert.equal(supportsObjectBrowser("mongodb"), false); + assert.equal(supportsTableTruncate("mysql"), true); + assert.equal(supportsTableTruncate("duckdb"), false); +});