fix(editor): unquote DDL view when identifier quoting disabled

This commit is contained in:
zipg authored and GitHub committed 2026-09-08 00:14:02 +08:00
1 parent 5814ff0088
commit 889fa4f5d0
7 files changed
+87 -5

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { omitDdlIdentifierQuotes } from "@/lib/sql/ddlDisplay";
describe("omitDdlIdentifierQuotes", () => {
it("removes safe MySQL identifier quotes without changing literals or comments", () => {
const ddl = "CREATE TABLE `demo_table` (`id` int DEFAULT 'a`b') COMMENT='`keep`'; -- `keep`";
expect(omitDdlIdentifierQuotes(ddl, "mysql")).toBe("CREATE TABLE demo_table (id int DEFAULT 'a`b') COMMENT='`keep`'; -- `keep`");
});
it("keeps quotes for names that require them", () => {
expect(omitDdlIdentifierQuotes("CREATE TABLE `order` (`with space` int)", "mysql")).toBe("CREATE TABLE `order` (`with space` int)");
});
it("supports PostgreSQL and SQL Server identifier delimiters", () => {
expect(omitDdlIdentifierQuotes('CREATE TABLE "demo_table" ("id" integer)', "postgres")).toBe("CREATE TABLE demo_table (id integer)");
expect(omitDdlIdentifierQuotes("CREATE TABLE [demo_table] ([id] int)", "sqlserver")).toBe("CREATE TABLE demo_table (id int)");
});
it("keeps brackets on SQL Server reserved words while unquoting safe names", () => {
const ddl = "CREATE TABLE [demo_table] ([order] int, [key] int, [user] nvarchar(50), [id] int)";
expect(omitDdlIdentifierQuotes(ddl, "sqlserver")).toBe("CREATE TABLE demo_table ([order] int, [key] int, [user] nvarchar(50), id int)");
});
});
+43
View File
@@ -0,0 +1,43 @@
import { requiresMysqlIdentifierQuote, requiresPostgresIdentifierQuote } from "@/lib/sql/sqlIdentifier";
import { tokenizeSqlSemantic, unquoteSqlSemanticIdentifier } from "@/lib/sql/semantic/tokens";
import type { SqlFormatDialect } from "@/lib/sql/sqlFormatter";
const SIMPLE_SQLSERVER_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
function canRenderUnquoted(identifier: string, dialect: SqlFormatDialect): boolean {
switch (dialect) {
case "mysql":
case "clickhouse":
return !requiresMysqlIdentifierQuote(identifier);
case "postgres":
case "sqlite":
case "oracle":
case "duckdb":
case "dameng":
case "generic":
return !requiresPostgresIdentifierQuote(identifier);
case "sqlserver":
return SIMPLE_SQLSERVER_IDENTIFIER.test(identifier) && !requiresMysqlIdentifierQuote(identifier.toLowerCase());
default:
return false;
}
}
/** 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);
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 (canRenderUnquoted(identifier, dialect)) replacements.push({ start: token.span.start, end: token.span.end, value: identifier });
}
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;
}