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 = DEFAULT_SQL_FORMATTER_SETTINGS): Promise { 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 = DEFAULT_SQL_FORMATTER_SETTINGS): Promise { if (!sql.trim()) return sql; try { return await formatSqlText(sql, dialect, settings); } catch { return sql; } }