diff --git a/src/App.vue b/src/App.vue index 0eb17ec34..0f869a181 100644 --- a/src/App.vue +++ b/src/App.vue @@ -33,6 +33,7 @@ import { resolveExecutableSql } from "@/lib/sqlExecutionTarget"; import { isTauriRuntime } from "@/lib/tauriRuntime"; import { isCloseTabShortcut, isExecuteSqlShortcut } from "@/lib/keyboardShortcuts"; import { isPreviewTab } from "@/lib/tabPresentation"; +import { SQL_FILE_UNSUPPORTED_TYPES } from "@/lib/databaseCapabilities"; const { t } = useI18n(); const connectionStore = useConnectionStore(); @@ -103,9 +104,8 @@ const { onExecuteSql, onReloadData, onPaginate, onSort } = useDataGridActions(ac const { setupTauriListeners } = useTauriEvents({ openTableTarget }); const appVersion = ref(""); -const sqlFileUnsupportedTypes = new Set(["redis", "mongodb", "elasticsearch"]); const hasSqlFileConnections = computed(() => - connectionStore.connections.some((c) => !sqlFileUnsupportedTypes.has(c.db_type)), + connectionStore.connections.some((c) => !SQL_FILE_UNSUPPORTED_TYPES.has(c.db_type)), ); const connectionStats = computed(() => ({ total: connectionStore.connections.length, diff --git a/src/components/diagram/SchemaDiagramDialog.vue b/src/components/diagram/SchemaDiagramDialog.vue index 08792c9c2..435406015 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 type { DatabaseType } from "@/types/database"; +import { DIAGRAM_SQL_TYPES, SCHEMA_AWARE_TYPES } from "@/lib/databaseCapabilities"; import { buildDiagramRelationships, filterDiagramTables, @@ -49,17 +49,6 @@ const props = defineProps<{ focusTableName?: string; }>(); -const SQL_TYPES: DatabaseType[] = [ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", -]; -const SCHEMA_AWARE_TYPES: DatabaseType[] = ["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"]; const CARD_WIDTH = 270; const COLUMN_ROW_HEIGHT = 24; const CARD_HEADER_HEIGHT = 44; @@ -96,12 +85,14 @@ const dragging = ref<{ originY: number; } | null>(null); -const sqlConnections = computed(() => store.connections.filter((connection) => SQL_TYPES.includes(connection.db_type))); +const sqlConnections = computed(() => + store.connections.filter((connection) => DIAGRAM_SQL_TYPES.has(connection.db_type)), +); const selectedConnection = computed(() => (connectionId.value ? store.getConfig(connectionId.value) : undefined)); const isSchemaAware = computed( - () => !!selectedConnection.value && SCHEMA_AWARE_TYPES.includes(selectedConnection.value.db_type), + () => !!selectedConnection.value && SCHEMA_AWARE_TYPES.has(selectedConnection.value.db_type), ); const allRelationships = computed(() => buildDiagramRelationships(tables.value)); diff --git a/src/components/diff/SchemaDiffDialog.vue b/src/components/diff/SchemaDiffDialog.vue index f24c0ff2a..7f8cdf811 100644 --- a/src/components/diff/SchemaDiffDialog.vue +++ b/src/components/diff/SchemaDiffDialog.vue @@ -9,6 +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 { isSchemaAware } from "@/lib/databaseCapabilities"; import { diffColumns, diffIndexes, diffTables, generateSyncSql, type TableDiff } from "@/lib/schemaDiff"; import { useToast } from "@/composables/useToast"; import { Loader2, Copy, Play, GitCompareArrows } from "lucide-vue-next"; @@ -72,13 +73,7 @@ async function loadDatabases(connectionId: string, side: "source" | "target") { async function resolveSchema(connectionId: string, database: string): Promise { const config = store.getConfig(connectionId); - const needsSchema = - config?.db_type === "postgres" || - config?.db_type === "sqlserver" || - config?.db_type === "oracle" || - config?.db_type === "redshift" || - config?.db_type === "dameng" || - config?.db_type === "gaussdb"; + const needsSchema = isSchemaAware(config?.db_type); if (needsSchema) { const schemas = await api.listSchemas(connectionId, database); return schemas.includes("public") ? "public" : (schemas[0] ?? ""); diff --git a/src/components/search/DatabaseSearchDialog.vue b/src/components/search/DatabaseSearchDialog.vue index 5e8a73bd6..4155ca722 100644 --- a/src/components/search/DatabaseSearchDialog.vue +++ b/src/components/search/DatabaseSearchDialog.vue @@ -12,6 +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"; const props = defineProps<{ open: boolean; @@ -54,7 +55,6 @@ type SearchTableTask = { table: TableInfo; }; -const SCHEMA_AWARE_TYPES = new Set(["postgres", "sqlserver", "oracle", "redshift", "dameng", "gaussdb"]); const SYSTEM_SCHEMAS = new Set([ "information_schema", "pg_catalog", diff --git a/src/components/sidebar/TreeItem.vue b/src/components/sidebar/TreeItem.vue index df12c6216..850d1b029 100644 --- a/src/components/sidebar/TreeItem.vue +++ b/src/components/sidebar/TreeItem.vue @@ -61,6 +61,18 @@ import { type ExportedTableSql, } from "@/lib/databaseExport"; import { qualifiedTableName as buildQualifiedTableName, quoteTableIdentifier } from "@/lib/tableSelectSql"; +import { + SQL_FILE_UNSUPPORTED_TYPES, + DIAGRAM_SUPPORTED_TYPES, + DATABASE_SEARCH_SUPPORTED_TYPES, + TABLE_IMPORT_SUPPORTED_TYPES, + TABLE_STRUCTURE_SUPPORTED_TYPES, + FIELD_LINEAGE_SUPPORTED_TYPES, + TREE_SCHEMA_TYPES, + PG_LIKE_STRUCTURE_TYPES, + isSchemaAware, + usesFetchFirst, +} from "@/lib/databaseCapabilities"; import { treeNodeRowAction } from "@/lib/treeNodeClick"; import DangerConfirmDialog from "@/components/editor/DangerConfirmDialog.vue"; import { isTauriRuntime } from "@/lib/tauriRuntime"; @@ -86,54 +98,6 @@ const emit = defineEmits<{ "rename-started": []; }>(); -const sqlFileUnsupportedTypes = new Set(["redis", "mongodb", "elasticsearch"]); -const diagramSupportedTypes = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", -]); -const databaseSearchSupportedTypes = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "duckdb", - "clickhouse", - "dameng", - "gaussdb", -]); -const tableImportSupportedTypes = new Set([ - "mysql", - "postgres", - "sqlite", - "duckdb", - "clickhouse", - "sqlserver", - "oracle", - "doris", - "starrocks", - "redshift", - "dameng", - "gaussdb", -]); -const tableStructureSupportedTypes = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); -const fieldLineageSupportedTypes = new Set([ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "redshift", - "dameng", - "gaussdb", -]); const isExportingDatabase = ref(false); function currentDatabaseType(): DatabaseType | undefined { @@ -144,16 +108,6 @@ function quoteIdent(name: string): string { return quoteTableIdentifier(currentDatabaseType(), name); } -function isSchemaAwareDbType(dbType?: DatabaseType): boolean { - return ( - dbType === "postgres" || - dbType === "oracle" || - dbType === "sqlserver" || - dbType === "dameng" || - dbType === "gaussdb" - ); -} - function qualifiedTableName(tableName: string, schema?: string): string { return buildQualifiedTableName({ databaseType: currentDatabaseType(), @@ -260,7 +214,7 @@ async function toggle() { queryStore.updateSql(tab, node.label); } else if (node.type === "database" && node.connectionId && node.database) { const config = connectionStore.getConfig(node.connectionId); - if (config?.db_type === "postgres" || config?.db_type === "sqlserver" || config?.db_type === "gaussdb") { + if (config?.db_type && TREE_SCHEMA_TYPES.has(config.db_type)) { await connectionStore.loadSchemas(node.connectionId, node.database); } else { await connectionStore.loadTables(node.connectionId, node.database); @@ -305,11 +259,7 @@ async function openData() { if (!config) throw new Error("Connection config not found"); const qualifiedName = - (config.db_type === "postgres" || - config.db_type === "oracle" || - config.db_type === "sqlserver" || - config.db_type === "dameng") && - node.schema + isSchemaAware(config.db_type) && node.schema ? `${quoteIdent(node.schema)}.${quoteIdent(node.label)}` : quoteIdent(node.label); @@ -318,7 +268,7 @@ async function openData() { const pks = columns.filter((c) => c.is_primary_key).map((c) => c.name); const order = pks.length ? ` ORDER BY ${pks.map((pk) => `${quoteIdent(pk)} ASC`).join(", ")}` : ""; let sql: string; - if (config.db_type === "oracle" || config.db_type === "dameng") { + if (usesFetchFirst(config.db_type)) { sql = `SELECT * FROM ${qualifiedName}${order} FETCH FIRST 100 ROWS ONLY`; } else if (config.db_type === "sqlserver") { sql = `SELECT TOP 100 * FROM ${qualifiedName}${order}`; @@ -400,7 +350,7 @@ const canCreateTable = computed(() => { (props.node.type === "database" || props.node.type === "schema") && !!props.node.database && !!config && - tableStructureSupportedTypes.has(config.db_type) + TABLE_STRUCTURE_SUPPORTED_TYPES.has(config.db_type) ); }); @@ -490,11 +440,11 @@ async function confirmDuplicateStructure() { let sql: string; if (dbType === "mysql") { sql = `CREATE TABLE ${target} LIKE ${source};`; - } else if (dbType === "postgres" || dbType === "redshift" || dbType === "gaussdb") { + } else if (dbType && PG_LIKE_STRUCTURE_TYPES.has(dbType)) { sql = `CREATE TABLE ${target} (LIKE ${source} INCLUDING ALL);`; } else if (dbType === "sqlserver") { sql = `SELECT TOP 0 * INTO ${target} FROM ${source};`; - } else if (dbType === "oracle" || dbType === "dameng") { + } else if (usesFetchFirst(dbType)) { sql = `CREATE TABLE ${target} AS SELECT * FROM ${source} WHERE 1=0`; } else { sql = `CREATE TABLE ${target} AS SELECT * FROM ${source} WHERE 0;`; @@ -536,7 +486,7 @@ async function collectDatabaseExportTables(): Promise { @@ -685,11 +635,7 @@ async function exportData(format: "csv" | "json" | "sql") { try { await connectionStore.ensureConnected(node.connectionId); const qualifiedName = - (config.db_type === "postgres" || - config.db_type === "oracle" || - config.db_type === "sqlserver" || - config.db_type === "dameng") && - node.schema + isSchemaAware(config.db_type) && node.schema ? `${quoteIdent(node.schema)}.${quoteIdent(node.label)}` : quoteIdent(node.label); const result = await api.executeQuery(node.connectionId, node.database, `SELECT * FROM ${qualifiedName}`); @@ -836,26 +782,29 @@ const canExpand = !leafTypes.has(props.node.type); const canPin = computed(() => pinnableTypes.has(props.node.type)); const canOpenSqlFileExecution = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; - return !!config && !sqlFileUnsupportedTypes.has(config.db_type); + return !!config && !SQL_FILE_UNSUPPORTED_TYPES.has(config.db_type); }); const canOpenDiagram = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; - return !!props.node.database && !!config && diagramSupportedTypes.has(config.db_type); + return !!props.node.database && !!config && DIAGRAM_SUPPORTED_TYPES.has(config.db_type); }); const canOpenDatabaseSearch = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; - return !!props.node.database && !!config && databaseSearchSupportedTypes.has(config.db_type); + return !!props.node.database && !!config && DATABASE_SEARCH_SUPPORTED_TYPES.has(config.db_type); }); const canOpenTableImport = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; return ( - props.node.type === "table" && !!props.node.database && !!config && tableImportSupportedTypes.has(config.db_type) + props.node.type === "table" && !!props.node.database && !!config && TABLE_IMPORT_SUPPORTED_TYPES.has(config.db_type) ); }); const canOpenStructureEditor = computed(() => { const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined; return ( - props.node.type === "table" && !!props.node.database && !!config && tableStructureSupportedTypes.has(config.db_type) + props.node.type === "table" && + !!props.node.database && + !!config && + TABLE_STRUCTURE_SUPPORTED_TYPES.has(config.db_type) ); }); const canOpenFieldLineage = computed(() => { @@ -865,7 +814,7 @@ const canOpenFieldLineage = computed(() => { !!props.node.database && !!props.node.tableName && !!config && - fieldLineageSupportedTypes.has(config.db_type) + FIELD_LINEAGE_SUPPORTED_TYPES.has(config.db_type) ); }); const isPinned = computed(() => props.node.pinned || connectionStore.isTreeNodePinned(props.node.id)); diff --git a/src/components/transfer/DataTransferDialog.vue b/src/components/transfer/DataTransferDialog.vue index 9da6aeece..f994af709 100644 --- a/src/components/transfer/DataTransferDialog.vue +++ b/src/components/transfer/DataTransferDialog.vue @@ -12,6 +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 { nextTransferTerminalState } from "@/lib/transferProgressState"; import { ArrowRightLeft, Check, X, Loader2, Square, CheckSquare } from "lucide-vue-next"; @@ -25,19 +26,7 @@ const props = defineProps<{ const store = useConnectionStore(); -const SQL_TYPES: DatabaseType[] = [ - "mysql", - "postgres", - "sqlite", - "sqlserver", - "oracle", - "clickhouse", - "duckdb", - "dameng", - "gaussdb", -]; - -const sqlConnections = computed(() => store.connections.filter((c) => SQL_TYPES.includes(c.db_type))); +const sqlConnections = computed(() => store.connections.filter((c) => TRANSFER_SQL_TYPES.has(c.db_type))); // Source state const sourceConnectionId = ref(""); @@ -131,12 +120,7 @@ async function loadTables() { loadingTables.value = true; try { const config = store.getConfig(sourceConnectionId.value); - const needsSchema = - config?.db_type === "postgres" || - config?.db_type === "sqlserver" || - config?.db_type === "oracle" || - config?.db_type === "dameng" || - config?.db_type === "gaussdb"; + const needsSchema = isSchemaAware(config?.db_type); if (needsSchema) { const schemas = await api.listSchemas(sourceConnectionId.value, sourceDatabase.value); sourceSchema.value = schemas.includes("public") ? "public" : (schemas[0] ?? ""); @@ -223,12 +207,7 @@ async function startTransfer() { // Auto-detect target schema const targetConfig = store.getConfig(targetConnectionId.value); - const targetNeedsSchema = - targetConfig?.db_type === "postgres" || - targetConfig?.db_type === "sqlserver" || - targetConfig?.db_type === "oracle" || - targetConfig?.db_type === "dameng" || - targetConfig?.db_type === "gaussdb"; + const targetNeedsSchema = isSchemaAware(targetConfig?.db_type); if (targetNeedsSchema && !targetSchema.value) { try { const schemas = await api.listSchemas(targetConnectionId.value, targetDatabase.value); diff --git a/src/composables/useSchemaOptions.ts b/src/composables/useSchemaOptions.ts index f466db2cf..25ca7561d 100644 --- a/src/composables/useSchemaOptions.ts +++ b/src/composables/useSchemaOptions.ts @@ -1,5 +1,6 @@ import { ref } from "vue"; import { useConnectionStore } from "@/stores/connectionStore"; +import { isSchemaAware as isSchemaAwareType } from "@/lib/databaseCapabilities"; import * as api from "@/lib/api"; export function useSchemaOptions() { @@ -13,14 +14,7 @@ export function useSchemaOptions() { } function isSchemaAware(connectionId: string): boolean { - const dbType = connectionStore.getConfig(connectionId)?.db_type; - return ( - dbType === "postgres" || - dbType === "sqlserver" || - dbType === "oracle" || - dbType === "dameng" || - dbType === "gaussdb" - ); + return isSchemaAwareType(connectionStore.getConfig(connectionId)?.db_type); } async function loadSchemaOptions(connectionId: string, database: string) { diff --git a/src/lib/databaseCapabilities.ts b/src/lib/databaseCapabilities.ts new file mode 100644 index 000000000..8aa555a48 --- /dev/null +++ b/src/lib/databaseCapabilities.ts @@ -0,0 +1,101 @@ +import type { DatabaseType } from "@/types/database"; + +export const SCHEMA_AWARE_TYPES = new Set([ + "postgres", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", +]); + +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", +]); + +export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "duckdb", + "clickhouse", + "dameng", + "gaussdb", +]); + +export const TABLE_IMPORT_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "duckdb", + "clickhouse", + "sqlserver", + "oracle", + "doris", + "starrocks", + "redshift", + "dameng", + "gaussdb", +]); + +export const TABLE_STRUCTURE_SUPPORTED_TYPES = new Set(["mysql", "postgres", "sqlite", "sqlserver"]); + +export const FIELD_LINEAGE_SUPPORTED_TYPES = new Set([ + "mysql", + "postgres", + "sqlite", + "sqlserver", + "oracle", + "redshift", + "dameng", + "gaussdb", +]); + +export const FETCH_FIRST_TYPES = new Set(["oracle", "dameng"]); + +export const TREE_SCHEMA_TYPES = new Set(["postgres", "sqlserver", "gaussdb"]); + +export const PG_LIKE_STRUCTURE_TYPES = new Set(["postgres", "redshift", "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 usesFetchFirst(dbType?: DatabaseType): boolean { + return !!dbType && FETCH_FIRST_TYPES.has(dbType); +} diff --git a/src/lib/databaseSearch.ts b/src/lib/databaseSearch.ts index b70a6b45b..810ba96a9 100644 --- a/src/lib/databaseSearch.ts +++ b/src/lib/databaseSearch.ts @@ -1,5 +1,6 @@ import type { ColumnInfo, DatabaseType } from "../types/database.ts"; import { qualifiedTableName, quoteTableIdentifier } from "./tableSelectSql.ts"; +import { usesFetchFirst } from "@/lib/databaseCapabilities"; export interface DatabaseSearchSqlOptions { databaseType?: DatabaseType; @@ -133,7 +134,7 @@ export function buildDatabaseSearchSql(options: DatabaseSearchSqlOptions): Datab }; } - if (options.databaseType === "oracle") { + if (usesFetchFirst(options.databaseType)) { return { sql: `SELECT * FROM ${table} WHERE (${where}) FETCH FIRST ${limit} ROWS ONLY`, searchableColumns, diff --git a/src/lib/tableSelectSql.ts b/src/lib/tableSelectSql.ts index d0eb2ec79..f91e1be99 100644 --- a/src/lib/tableSelectSql.ts +++ b/src/lib/tableSelectSql.ts @@ -1,4 +1,5 @@ import type { DatabaseType } from "../types/database.ts"; +import { isSchemaAware, usesFetchFirst } from "@/lib/databaseCapabilities"; export interface BuildTableSelectSqlOptions { databaseType?: DatabaseType; @@ -22,14 +23,7 @@ export function qualifiedTableName( options: Pick, ): string { const { databaseType, schema, tableName } = options; - if ( - (databaseType === "postgres" || - databaseType === "oracle" || - databaseType === "sqlserver" || - databaseType === "dameng" || - databaseType === "gaussdb") && - schema - ) { + if (isSchemaAware(databaseType) && schema) { return `${quoteTableIdentifier(databaseType, schema)}.${quoteTableIdentifier(databaseType, tableName)}`; } return quoteTableIdentifier(databaseType, tableName); @@ -54,7 +48,7 @@ export function buildTableSelectSql(options: BuildTableSelectSqlOptions): string const orderBy = options.orderBy ?? defaultOrderBy; const order = orderBy ? ` ORDER BY ${orderBy}` : ""; - if (databaseType === "oracle" || databaseType === "dameng") { + if (usesFetchFirst(databaseType)) { const offset = options.offset ? ` OFFSET ${options.offset} ROWS` : ""; return `SELECT * FROM ${table}${where}${order}${offset} FETCH FIRST ${limit} ROWS ONLY`; } diff --git a/src/stores/connectionStore.ts b/src/stores/connectionStore.ts index cf212a2bc..e391b85ae 100644 --- a/src/stores/connectionStore.ts +++ b/src/stores/connectionStore.ts @@ -20,6 +20,7 @@ import { import type { SqlCompletionColumn, SqlCompletionTable } from "@/lib/sqlCompletion"; import * as api from "@/lib/api"; import { isTauriRuntime } from "@/lib/tauriRuntime"; +import { isSchemaAware } from "@/lib/databaseCapabilities"; const PINNED_TREE_NODES_STORAGE_KEY = "dbx-pinned-tree-nodes"; @@ -653,14 +654,7 @@ export const useConnectionStore = defineStore("connection", () => { } function isSchemaAwareDatabase(connectionId: string): boolean { - const dbType = getConfig(connectionId)?.db_type; - return ( - dbType === "postgres" || - dbType === "sqlserver" || - dbType === "oracle" || - dbType === "dameng" || - dbType === "gaussdb" - ); + return isSchemaAware(getConfig(connectionId)?.db_type); } async function listCompletionTables(connectionId: string, database: string): Promise {