From d8943706c0b36ebb7409e9e6a4600e5635af067a Mon Sep 17 00:00:00 2001 From: zipg Date: Sat, 17 Oct 2026 15:13:31 +0800 Subject: [PATCH] fix(sql): keep semicolonless statements on separate lines --- .../src/lib/__tests__/sql/sqlLayout.spec.ts | 24 ++++++++++++++++++ apps/desktop/src/lib/sql/layout/primitives.ts | 24 +++++++++++++++++- docs/assets/pr-12523/README.md | 5 ++++ docs/assets/pr-12523/sql-format-newlines.png | Bin 0 -> 29073 bytes 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 docs/assets/pr-12523/README.md create mode 100644 docs/assets/pr-12523/sql-format-newlines.png diff --git a/apps/desktop/src/lib/__tests__/sql/sqlLayout.spec.ts b/apps/desktop/src/lib/__tests__/sql/sqlLayout.spec.ts index 37c6b3b581..8e67d4cda0 100644 --- a/apps/desktop/src/lib/__tests__/sql/sqlLayout.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/sqlLayout.spec.ts @@ -19,6 +19,30 @@ async function format(sql: string, overrides: Partial = {} const lines = (...rows: string[]) => rows.join("\n"); describe("sql layout", () => { + it.each([ + ["SELECT 1\nSELECT 2", "SELECT 1\nSELECT 2"], + ["SELECT a FROM t\nSELECT b FROM u", "SELECT a\nFROM t\nSELECT b\nFROM u"], + ["SELECT 1\nUPDATE t SET a = 2", "SELECT 1\nUPDATE t\nSET a = 2"], + ["UPDATE t SET a = 1\nUPDATE t SET a = 2", "UPDATE t\nSET a = 1\nUPDATE t\nSET a = 2"], + ["SET @a = 1\nSELECT @a", "SET @a = 1\nSELECT @a"], + ["DELETE FROM t\nINSERT INTO t VALUES (1)", "DELETE FROM t\nINSERT INTO t\nVALUES (1)"], + ["SELECT 1\nSHOW TABLES", "SELECT 1\nSHOW TABLES"], + ])("keeps unseparated statements on different lines (#12523): %s", async (sql, expected) => { + const formatted = await format(sql); + expect(formatted).toBe(expected); + expect(await format(formatted)).toBe(formatted); + expect(formatted).not.toContain(";"); + }); + + it.each([ + ["SELECT (SELECT 1) AS value", "SELECT (SELECT 1) AS value"], + ["WITH t AS (SELECT 1 AS a) SELECT a FROM t", "WITH t AS (SELECT 1 AS a) SELECT a FROM t"], + ["SELECT 'SELECT UPDATE' AS value", "SELECT 'SELECT UPDATE' AS value"], + ["UPDATE t SET a = 1 WHERE id = 2", "UPDATE t SET a = 1 WHERE id = 2"], + ])("keeps a single short statement compact: %s", async (sql, expected) => { + expect(await format(sql)).toBe(expected); + }); + it("formats a query with a derived table (#827)", async () => { const sql = lines( "SELECT loc_id, loc_name, loc_type, delivery_emp_num, update_time", diff --git a/apps/desktop/src/lib/sql/layout/primitives.ts b/apps/desktop/src/lib/sql/layout/primitives.ts index 7555df85e6..60fbb30f41 100644 --- a/apps/desktop/src/lib/sql/layout/primitives.ts +++ b/apps/desktop/src/lib/sql/layout/primitives.ts @@ -411,6 +411,28 @@ export function clauseChildContext(ctx: SqlLayoutContext, clause: ClauseNode): S return { ...ctx, nonCallParens: new Set([...(ctx.nonCallParens ?? []), clause.children[at]]) }; } +// The parser groups everything up to a semicolon into one StatementNode, +// including scripts whose statements are separated only by newlines. Do not +// collapse several statement heads onto one line. Only inspect sibling clauses: +// SELECTs in CTEs, subqueries, strings and comments must not count here. +function hasMultipleStatementHeads(nodes: AstNode[]): boolean { + let seenHead = false; + let updateNeedsSet = false; + for (const node of nodes) { + if (node.type !== "clause") continue; + const keyword = node.nameKw.text.toUpperCase(); + if (keyword === "SET" && updateNeedsSet) { + updateNeedsSet = false; + continue; + } + if (!/^(?:SELECT|INSERT|REPLACE|UPDATE|DELETE|MERGE|CREATE|ALTER|DROP|TRUNCATE|SHOW|DESCRIBE|DESC|EXPLAIN|USE|SET|CALL|EXEC|EXECUTE|GRANT|REVOKE|COMMIT|ROLLBACK)\b/.test(keyword)) continue; + if (seenHead) return true; + seenHead = true; + updateNeedsSet = keyword === "UPDATE"; + } + return false; +} + /** * Renders `nodes` on one line, or `null` when they do not fit in `width`. * @@ -429,7 +451,7 @@ export function clauseChildContext(ctx: SqlLayoutContext, clause: ClauseNode): S * while it fits; the printer splits the operators onto their own lines otherwise. */ export function renderInline(ctx: SqlLayoutContext, nodes: AstNode[], width: number): string | null { - if (nodes.length === 0 || width <= 0) return null; + if (nodes.length === 0 || width <= 0 || hasMultipleStatementHeads(nodes)) return null; const parenAt = nodes.findIndex(isParenthesis); if (parenAt >= 0) { diff --git a/docs/assets/pr-12523/README.md b/docs/assets/pr-12523/README.md new file mode 100644 index 0000000000..181c71e4d4 --- /dev/null +++ b/docs/assets/pr-12523/README.md @@ -0,0 +1,5 @@ +# #12523 修复实测 + +`sql-format-newlines.png` 展示本分支真实 `QueryEditor.vue` 组件的格式化结果。 +在独立的本地 Vue 页面中选择 DBX 排版风格,输入两行无分号的 SELECT 后点击格式化;两条查询仍各占一行,没有插入分号。 +此页面使用生产组件和格式化代码,未连接数据库,也未执行 SQL。 diff --git a/docs/assets/pr-12523/sql-format-newlines.png b/docs/assets/pr-12523/sql-format-newlines.png new file mode 100644 index 0000000000000000000000000000000000000000..1b35fa7f28718041862e7487cbb959828e20aa6d GIT binary patch literal 29073 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 z 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 Bjcxz{ literal 0 HcmV?d00001