fix(editor): preserve trailing Dameng dot whitespace
This commit is contained in:
2 files changed
+75
-16
No files matched your search
@@ -14,6 +14,10 @@ describe("sqlFormatter", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("maps Dameng to its scoped formatter dialect", () => {
|
||||
expect(sqlFormatDialectForDbType("dameng")).toBe("dameng");
|
||||
});
|
||||
|
||||
it("preserves ClickHouse lambda arrows when formatting issue #3573 SQL", async () => {
|
||||
const sql = `
|
||||
WITH industry_code_donghua_id_RYCzfD AS (SELECT id
|
||||
@@ -50,6 +54,35 @@ describe("sqlFormatter", () => {
|
||||
expect(formatted).toContain("AS id");
|
||||
});
|
||||
|
||||
it("formats Dameng SQL with a standalone trailing dot without changing the invalid token", async () => {
|
||||
const sql = `SELECT JS1.REC_CREATOR as "recCreator", JS1.REC_CREATOR_JOB_ID as "recCreatorJobId" FROM APSSC.TMPJS01 JS1 WHERE 1=1 AND JS1.SUBSTR(REC_CREATE_TIME,1,8) = ? ORDER BY DECODE(JS1.STATUS,'DRAFT',1,'PENDING_APPROVAL',2,'APPROVED',3,'POSTED',4,'REJECTED',5,'DELETED',6), JS1.REC_CREATE_TIME DESC .`;
|
||||
|
||||
const formatted = await formatSqlForEditing(sql, sqlFormatDialectForDbType("dameng"));
|
||||
|
||||
expect(formatted).toContain('JS1.REC_CREATOR AS "recCreator"');
|
||||
expect(formatted).toContain("DECODE (");
|
||||
expect(formatted.endsWith("JS1.REC_CREATE_TIME DESC .")).toBe(true);
|
||||
});
|
||||
|
||||
it("only recovers a whitespace-separated final dot", async () => {
|
||||
await expect(formatSqlText("SELECT schema.", "dameng")).rejects.toThrow();
|
||||
await expect(formatSqlText("SELECT 1..", "dameng")).rejects.toThrow();
|
||||
await expect(formatSqlText("SELECT 'value .'", "dameng")).resolves.toContain("'value .'");
|
||||
});
|
||||
|
||||
it("preserves whitespace after a recovered trailing dot", async () => {
|
||||
await expect(formatSqlForEditing("SELECT 1 .\n", "dameng")).resolves.toBe("SELECT\n 1 .\n");
|
||||
});
|
||||
|
||||
it("preserves the newline before a trailing dot after a line comment", async () => {
|
||||
await expect(formatSqlForEditing("DELETE FROM accounts -- comment\n .", "dameng")).resolves.toBe("DELETE FROM accounts -- comment\n .");
|
||||
});
|
||||
|
||||
it("does not change trailing-dot formatting for other databases", async () => {
|
||||
await expect(formatSqlText("SELECT 1 .", "generic")).rejects.toThrow();
|
||||
await expect(formatSqlForEditing("SELECT 1 .", "generic")).resolves.toBe("SELECT 1 .");
|
||||
});
|
||||
|
||||
it("keeps incomplete editor SQL unchanged when the formatter cannot parse it", async () => {
|
||||
const sql = "select *\nfrom dbname.\n;";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DEFAULT_SQL_FORMATTER_SETTINGS, normalizeSqlFormatterSettings, sqlFormatterOptions, type SqlFormatterSettings } from "@/lib/sql/sqlFormatterConfig";
|
||||
import { looksLikeXml } from "@/lib/sql/autoFormat";
|
||||
|
||||
export type SqlFormatDialect = "mysql" | "postgres" | "sqlite" | "sqlserver" | "clickhouse" | "generic";
|
||||
export type SqlFormatDialect = "mysql" | "postgres" | "sqlite" | "sqlserver" | "clickhouse" | "dameng" | "generic";
|
||||
|
||||
export const MAX_SQL_FORMAT_CHARS = 1_000_000;
|
||||
|
||||
@@ -53,6 +53,8 @@ export function sqlFormatDialectForDbType(dbType: string | null | undefined): Sq
|
||||
return "sqlserver";
|
||||
case "clickhouse":
|
||||
return "clickhouse";
|
||||
case "dameng":
|
||||
return "dameng";
|
||||
default:
|
||||
return "generic";
|
||||
}
|
||||
@@ -75,6 +77,16 @@ function formatterLanguage(dialect: SqlFormatDialect) {
|
||||
}
|
||||
}
|
||||
|
||||
function splitTrailingStandaloneDot(sql: string): { body: string; suffix: string } | null {
|
||||
const match = /\s+\.(\s*)$/.exec(sql);
|
||||
if (!match || match.index === 0) return null;
|
||||
|
||||
return {
|
||||
body: sql.slice(0, match.index),
|
||||
suffix: match[0],
|
||||
};
|
||||
}
|
||||
|
||||
export async function formatSqlText(sql: string, dialect: SqlFormatDialect = "generic", settings: Partial<SqlFormatterSettings> = DEFAULT_SQL_FORMATTER_SETTINGS): Promise<string> {
|
||||
if (!sql.trim()) return sql;
|
||||
if (sql.length > MAX_SQL_FORMAT_CHARS) {
|
||||
@@ -89,23 +101,37 @@ export async function formatSqlText(sql: string, dialect: SqlFormatDialect = "ge
|
||||
const normalizedSettings = normalizeSqlFormatterSettings(settings);
|
||||
const options = sqlFormatterOptions(normalizedSettings);
|
||||
const language = formatterLanguage(dialect);
|
||||
try {
|
||||
const formatted = format(sql, { language, ...options });
|
||||
return applySqlFormatterLayout(formatted, normalizedSettings, dialect);
|
||||
} catch (err) {
|
||||
// The generic "sql" dialect can't parse many real-world constructs (PostgreSQL
|
||||
// `::` casts, GaussDB/openGauss materialized-view DDL, T-SQL specifics, ...).
|
||||
// Retry once with the more permissive PostgreSQL grammar, which is a superset
|
||||
// that tolerates most of these, before surfacing the failure.
|
||||
if (language !== "postgresql") {
|
||||
try {
|
||||
const formatted = format(sql, { language: "postgresql", ...options });
|
||||
return applySqlFormatterLayout(formatted, normalizedSettings, dialect);
|
||||
} catch {
|
||||
// fall through to the original error below
|
||||
const formatWithFallback = (input: string): string => {
|
||||
try {
|
||||
return format(input, { language, ...options });
|
||||
} catch (err) {
|
||||
// The generic "sql" dialect can't parse many real-world constructs (PostgreSQL
|
||||
// `::` casts, GaussDB/openGauss materialized-view DDL, T-SQL specifics, ...).
|
||||
// Retry once with the more permissive PostgreSQL grammar, which is a superset
|
||||
// that tolerates most of these, before surfacing the failure.
|
||||
if (language !== "postgresql") {
|
||||
try {
|
||||
return format(input, { language: "postgresql", ...options });
|
||||
} catch {
|
||||
// fall through to the original error below
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return applySqlFormatterLayout(formatWithFallback(sql), normalizedSettings, dialect);
|
||||
} catch (err) {
|
||||
const trailingDot = dialect === "dameng" ? splitTrailingStandaloneDot(sql) : null;
|
||||
if (!trailingDot) throw err;
|
||||
|
||||
try {
|
||||
const formatted = applySqlFormatterLayout(formatWithFallback(trailingDot.body), normalizedSettings, dialect);
|
||||
return `${formatted}${trailingDot.suffix}`;
|
||||
} catch {
|
||||
throw err;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user