diff --git a/apps/desktop/src/components/objects/DdlViewDialog.vue b/apps/desktop/src/components/objects/DdlViewDialog.vue index dc81b0a48f..8488a158c1 100644 --- a/apps/desktop/src/components/objects/DdlViewDialog.vue +++ b/apps/desktop/src/components/objects/DdlViewDialog.vue @@ -11,7 +11,7 @@ import { loadEditorTheme, editorFontTheme } from "@/lib/editor/editorThemes"; import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect"; import { copyToClipboard } from "@/lib/common/clipboard"; import { formatSqlForDisplay, type SqlFormatDialect } from "@/lib/sql/sqlFormatter"; -import { omitDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay"; +import { ddlFormatDialectFor, omitDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay"; import { loadObjectDdl } from "@/lib/metadata/objectDdlCache"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; @@ -129,7 +129,7 @@ async function loadDdl(force = false) { }, { force }, ); - const formatDialect = props.formatDialect ?? props.dialect; + const formatDialect = ddlFormatDialectFor({ formatDialect: props.formatDialect, databaseType: props.databaseType, highlightDialect: props.dialect }); const formatted = await formatSqlForDisplay(ddl, formatDialect, settingsStore.editorSettings.sqlFormatter); const unqualified = omitDdlDatabaseQualifier(formatted, formatDialect, props.databaseType, settingsStore.editorSettings.generateSqlIncludeDatabaseName, props.catalog); rawDdlContent.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers ? unqualified : omitDdlIdentifierQuotes(unqualified, formatDialect); diff --git a/apps/desktop/src/lib/sql/__tests__/ddlDisplay.spec.ts b/apps/desktop/src/lib/sql/__tests__/ddlDisplay.spec.ts index 3d87216ce8..eb935f9a04 100644 --- a/apps/desktop/src/lib/sql/__tests__/ddlDisplay.spec.ts +++ b/apps/desktop/src/lib/sql/__tests__/ddlDisplay.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { formatGeneratedDdlIdentifierQuotes, omitDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay"; +import { ddlFormatDialectFor, formatGeneratedDdlIdentifierQuotes, omitDdlDatabaseQualifier, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay"; describe("omitDdlDatabaseQualifier", () => { it("drops the schema qualifier from Oracle table DDL without touching tablespace references", () => { @@ -109,3 +109,29 @@ describe("omitDdlIdentifierQuotes", () => { expect(formatGeneratedDdlIdentifierQuotes(ddl, "oracle", true)).toBe(ddl); }); }); + +describe("ddlFormatDialectFor", () => { + it("prefers the effective database type over an Oracle-like connection's MySQL highlighting dialect", () => { + expect(ddlFormatDialectFor({ databaseType: "oracle", highlightDialect: "mysql" })).toBe("oracle"); + expect(ddlFormatDialectFor({ databaseType: "dameng", highlightDialect: "mysql" })).toBe("dameng"); + expect(ddlFormatDialectFor({ databaseType: "kingbase", highlightDialect: "mysql" })).toBe("postgres"); + }); + + it("honors an explicit formatter dialect first", () => { + expect(ddlFormatDialectFor({ formatDialect: "sqlserver", databaseType: "oracle", highlightDialect: "mysql" })).toBe("sqlserver"); + }); + + it("keeps the highlighting dialect when the database type has no dedicated formatter dialect", () => { + expect(ddlFormatDialectFor({ databaseType: "db2", highlightDialect: "mysql" })).toBe("mysql"); + expect(ddlFormatDialectFor({ highlightDialect: "postgres" })).toBe("postgres"); + expect(ddlFormatDialectFor({})).toBe("generic"); + }); + + it("keeps case-sensitive quoted Oracle names quoted in the DDL view", () => { + const ddl = 'CREATE TABLE "SYSTEM"."TEST" ("id" VARCHAR2(32), "cName" VARCHAR2(100))'; + const highlightDialect = "mysql"; + const resolved = ddlFormatDialectFor({ databaseType: "oracle", highlightDialect }); + expect(omitDdlIdentifierQuotes(ddl, highlightDialect)).toBe("CREATE TABLE SYSTEM.TEST (id VARCHAR2(32), cName VARCHAR2(100))"); + expect(omitDdlIdentifierQuotes(ddl, resolved)).toBe('CREATE TABLE SYSTEM.TEST ("id" VARCHAR2(32), "cName" VARCHAR2(100))'); + }); +}); diff --git a/apps/desktop/src/lib/sql/ddlDisplay.ts b/apps/desktop/src/lib/sql/ddlDisplay.ts index db3fbc69c8..561d9a49ab 100644 --- a/apps/desktop/src/lib/sql/ddlDisplay.ts +++ b/apps/desktop/src/lib/sql/ddlDisplay.ts @@ -1,7 +1,7 @@ import { requiresDamengIdentifierQuote, requiresMysqlIdentifierQuote, requiresOracleIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier"; import { tokenIsIdentifier, tokenizeSqlSemantic, unquoteSqlSemanticIdentifier } from "@/lib/sql/semantic/tokens"; import type { SqlSemanticToken } from "@/lib/sql/semantic/types"; -import type { SqlFormatDialect } from "@/lib/sql/sqlFormatter"; +import { sqlFormatDialectForDbType, type SqlFormatDialect } from "@/lib/sql/sqlFormatter"; import { dropsSchemaQualifier } from "@/lib/table/tableSelectSql"; import type { DatabaseType } from "@/types/database"; @@ -28,6 +28,21 @@ function canRenderUnquoted(identifier: string, dialect: SqlFormatDialect): boole } } +/** + * Resolve the dialect used to read and rewrite DDL text. Callers that only + * know the connection's syntax-highlighting dialect (which collapses every + * Oracle-like database onto MySQL) must not have that value decide identifier + * quoting rules: a case-sensitive Oracle/Dameng name such as `"cName"` would + * lose its quotes. The effective database type wins, the highlighting dialect + * stays as the fallback for unmapped types. + */ +export function ddlFormatDialectFor(options: { formatDialect?: SqlFormatDialect; databaseType?: DatabaseType; highlightDialect?: SqlFormatDialect }): SqlFormatDialect { + if (options.formatDialect) return options.formatDialect; + const fromDatabaseType = options.databaseType ? sqlFormatDialectForDbType(options.databaseType) : undefined; + if (fromDatabaseType && fromDatabaseType !== "generic") return fromDatabaseType; + return options.highlightDialect ?? fromDatabaseType ?? "generic"; +} + /** Removes dialect identifier quotes from safe names while preserving strings, comments, and unsafe names. */ export function omitDdlIdentifierQuotes(sql: string, dialect: SqlFormatDialect): string { const tokens = tokenizeSqlSemantic(sql, dialect === "sqlserver" ? "sqlserver" : dialect);