100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import { DEFAULT_SQL_FORMATTER_SETTINGS, sqlFormatterOptions, type SqlFormatterSettings } from "@/lib/sqlFormatterConfig";
|
|
|
|
export type SqlFormatDialect = "mysql" | "postgres" | "sqlite" | "sqlserver" | "generic";
|
|
|
|
export const MAX_SQL_FORMAT_CHARS = 1_000_000;
|
|
|
|
/**
|
|
* Maps a connection's database type to the SQL-formatter dialect to use.
|
|
*
|
|
* Postgres-compatible engines (GaussDB/openGauss/Kingbase/...) reuse the
|
|
* "postgres" grammar, SQLite-compatible ones reuse "sqlite", and anything
|
|
* unrecognized falls back to the permissive "generic" dialect. Centralized
|
|
* here so every surface that formats SQL (editor, object source, DDL viewers)
|
|
* stays in sync.
|
|
*/
|
|
export function sqlFormatDialectForDbType(dbType: string | null | undefined): SqlFormatDialect {
|
|
switch (dbType) {
|
|
case "mysql":
|
|
return "mysql";
|
|
case "postgres":
|
|
case "kwdb":
|
|
case "gaussdb":
|
|
case "opengauss":
|
|
case "questdb":
|
|
case "kingbase":
|
|
case "highgo":
|
|
case "vastbase":
|
|
case "redshift":
|
|
return "postgres";
|
|
case "sqlite":
|
|
case "rqlite":
|
|
case "turso":
|
|
return "sqlite";
|
|
case "sqlserver":
|
|
return "sqlserver";
|
|
default:
|
|
return "generic";
|
|
}
|
|
}
|
|
|
|
function formatterLanguage(dialect: SqlFormatDialect) {
|
|
switch (dialect) {
|
|
case "mysql":
|
|
return "mysql";
|
|
case "postgres":
|
|
return "postgresql";
|
|
case "sqlite":
|
|
return "sqlite";
|
|
case "sqlserver":
|
|
return "transactsql";
|
|
default:
|
|
return "sql";
|
|
}
|
|
}
|
|
|
|
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) {
|
|
throw new Error("SQL is too large to format safely.");
|
|
}
|
|
|
|
const { format } = await import("sql-formatter");
|
|
const options = sqlFormatterOptions(settings);
|
|
const language = formatterLanguage(dialect);
|
|
try {
|
|
return format(sql, { 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(sql, { language: "postgresql", ...options });
|
|
} catch {
|
|
// fall through to the original error below
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Format SQL for *display* (object source, view/table DDL viewers).
|
|
*
|
|
* Unlike `formatSqlText`, this never throws: if the SQL can't be parsed by the
|
|
* formatter (vendor-specific DDL, oversized input, ...) the original text is
|
|
* returned unchanged so the viewer still shows the source. Use this for
|
|
* read-only/auto-format surfaces; use `formatSqlText` where a thrown error
|
|
* should surface to the user (e.g. the explicit "Format SQL" command).
|
|
*/
|
|
export async function formatSqlForDisplay(sql: string, dialect: SqlFormatDialect = "generic", settings: Partial<SqlFormatterSettings> = DEFAULT_SQL_FORMATTER_SETTINGS): Promise<string> {
|
|
if (!sql.trim()) return sql;
|
|
try {
|
|
return await formatSqlText(sql, dialect, settings);
|
|
} catch {
|
|
return sql;
|
|
}
|
|
}
|