fix(structure): unquote plain Oracle identifiers when quoting off

This commit is contained in:
zipg
2026-09-14 12:37:57 +08:00
committed by GitHub
parent 18e34bf33a
commit ac2dcc1b79
3 changed files with 49 additions and 3 deletions
@@ -27,7 +27,7 @@ import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect
import { useToast } from "@/composables/useToast";
import { type SqlHighlighter, createShikiSqlHighlighter } from "@/lib/sql/sqlHighlighter";
import { joinSqlStatementsForScript } from "@/lib/sql/sqlBatchScript";
import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { formatGeneratedDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { splitSqlStatementRanges } from "@/lib/sql/sqlStatementRanges";
import { copyToClipboard } from "@/lib/common/clipboard";
import { formatSqlForDisplay, sqlFormatDialectForDbType } from "@/lib/sql/sqlFormatter";
@@ -1767,7 +1767,7 @@ async function refreshSqlPreview() {
if (requestId !== sqlPreviewRequestId) return;
const statements = [...result.statements, ...ownerResult.statements, ...(mysqlAutoIncrementStatement ? [mysqlAutoIncrementStatement] : [])];
// SQLite type-change apply regenerates this revision-checked plan, so its preview must stay byte-for-byte aligned.
pendingStatements.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers || hasSqliteTypeChange.value ? statements : statements.map((statement) => omitDdlIdentifierQuotes(statement, sqlFormatDialectForDbType(databaseType.value)));
pendingStatements.value = settingsStore.editorSettings.generateSqlQuoteIdentifiers !== false || hasSqliteTypeChange.value ? statements : statements.map((statement) => formatGeneratedDdlIdentifierQuotes(statement, sqlFormatDialectForDbType(databaseType.value), false));
warnings.value = [...result.warnings, ...ownerResult.warnings];
sqliteSchemaRevision.value = "schemaRevision" in result && typeof result.schemaRevision === "string" ? result.schemaRevision : undefined;
} catch (e: any) {
+16 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
import { formatGeneratedDdlIdentifierQuotes, omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
describe("omitDdlIdentifierQuotes", () => {
it("removes safe MySQL identifier quotes without changing literals or comments", () => {
@@ -40,4 +40,19 @@ describe("omitDdlIdentifierQuotes", () => {
const ddl = 'CREATE TABLE "CamelCase" ("lowercase" INT, "WITH SPACE" INT, "ORDER" INT, "A$B" INT)';
expect(omitDdlIdentifierQuotes(ddl, "dameng")).toBe(ddl);
});
it("folds safe Oracle identifiers when generated SQL quoting is disabled", () => {
const ddl = 'ALTER TABLE "SYSTEM"."TEST" RENAME COLUMN "ABCD" TO "AbCd";';
expect(formatGeneratedDdlIdentifierQuotes(ddl, "oracle", false)).toBe("ALTER TABLE SYSTEM.TEST RENAME COLUMN ABCD TO ABCD;");
});
it("keeps case-sensitive Dameng identifiers quoted when generated SQL quoting is disabled", () => {
const ddl = 'ALTER TABLE "user_login_log" ADD "status" INT; ALTER TABLE "CamelCase" ADD "order" INT;';
expect(formatGeneratedDdlIdentifierQuotes(ddl, "dameng", false)).toBe(ddl);
});
it("preserves generated identifier quotes when the setting is enabled", () => {
const ddl = 'ALTER TABLE "SYSTEM"."TEST" RENAME COLUMN "ABCD" TO "AbCd";';
expect(formatGeneratedDdlIdentifierQuotes(ddl, "oracle", true)).toBe(ddl);
});
});
+31
View File
@@ -43,3 +43,34 @@ export function omitDdlIdentifierQuotes(sql: string, dialect: SqlFormatDialect):
}
return result;
}
const SIMPLE_ORACLE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_$#]*$/;
/**
* Applies the identifier-quoting preference to SQL generated by a structure
* editor. Oracle folds bare identifiers to uppercase, so safe quoted names
* are uppercased before their quotes are removed. Dameng keeps its stricter
* case-sensitive identifier rules through omitDdlIdentifierQuotes instead.
*/
export function formatGeneratedDdlIdentifierQuotes(sql: string, dialect: SqlFormatDialect, quoteIdentifiers: boolean): string {
if (quoteIdentifiers) return sql;
if (dialect !== "oracle") return omitDdlIdentifierQuotes(sql, dialect);
const tokens = tokenizeSqlSemantic(sql, dialect);
const replacements: Array<{ start: number; end: number; value: string }> = [];
for (const token of tokens) {
if (token.kind !== "quoted_identifier") continue;
const identifier = unquoteSqlSemanticIdentifier(token);
if (SIMPLE_ORACLE_IDENTIFIER.test(identifier) && !requiresOracleIdentifierQuote(identifier.toUpperCase())) {
replacements.push({ start: token.span.start, end: token.span.end, value: identifier.toUpperCase() });
}
}
let result = sql;
for (let index = replacements.length - 1; index >= 0; index -= 1) {
const replacement = replacements[index]!;
result = `${result.slice(0, replacement.start)}${replacement.value}${result.slice(replacement.end)}`;
}
return result;
}