Skip to content

feat(format): lay out clauses, parenthesised groups and windows as SQLQuack 0.6.0 does - #67

Merged
kvlonge merged 3 commits into
mainfrom
feat/format-sqlquack-060
Nov 12, 2026
Merged

kvlonge merged 3 commits into
mainfrom
feat/format-sqlquack-060

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

Why

SQLClean's formatter is SQLQuack's. SQLQuack 0.6.0 (tag v0.6.0, 8d28aa4c4b6c3d8c5345ddef3d620fa24e26741a) changes three layout rules, so SQLClean output changes with it. SQLBuild links SQLClean and SQLQuack into one binary, so both must pin the same revision.

Changes

  • Cargo.toml/Cargo.lock: every sqlquack-* pin moves from e1a583eb (0.5.1) to 8d28aa4c (0.6.0).
  • One-line clauses stay on their header (35 corpus entries). ORDER BY\n a DESC → ORDER BY a DESC; JOIN b\n ON b.id = a.id → JOIN b ON b.id = a.id.
  • A parenthesised group that spans lines gets its own lines (15 cases, 2 also above). ARRAY((\n SELECT …\n)) → ARRAY(\n (\n SELECT …\n )\n).
  • A window spec too wide for one line is broken (1 case). SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN …) → OVER (\n PARTITION BY y\n ORDER BY interval\n ROWS BETWEEN …\n ).
  • Parse errors name what was expected: Unexpected token: By → Expected an expression after BY.
  • format_corpus.json: 49 formatted updated (48 queries; one is listed twice), old layout kept as previously_formatted; every new value equals SQLQuack 0.6.0's own corpus. sqlquack_departures.json: one tsql departure dropped (now matches), two updated. Two inline expectations in the layout-rule and VALUES tests. Full before/after list in the first comment.

Verification

  • cargo test --release --workspace --locked, make check-ci, make test (72 pytest) pass.
  • Corpus dump, 10,017 queries × 7 dialects: SQLQuack 0.6.0 corpora basics, everyday, everyday-models, duckdb-core, databricks-core plus this repo's crates/sqlclean/src/sql_lint/tests/fixtures/format_corpus.json. No fingerprint changes and no formatted/refused flips. 7 invalid Snowflake DDL statements now fail to parse; CREATE TABLE … CLUSTER BY … AS SELECT now parses.

All six direct sqlquack-* pins (11 locked packages) move from e1a583eb
(0.5.1) to 8d28aa4c (0.6.0), so SQLBuild can link one SQLQuack copy.

Work in progress: SQLQuack 0.6.0 changes the formatter's layout, and the
format expectations are not updated yet.
…LQuack 0.6.0 does

SQLQuack 0.6.0 changes three layout rules, and SQLClean's formatter is
SQLQuack's:

- A clause with a single short item stays on its header line
  (ORDER BY a DESC, GROUP BY customer_id, JOIN b ON b.id = a.id).
- A parenthesised group that spans lines gets its own lines instead of
  hugging the call's parenthesis (ARRAY(\n  (\n    SELECT ...)).
- A window specification too wide for one line is broken into
  PARTITION BY, ORDER BY and frame lines.

Parse errors now name what was expected ("Expected an expression after
BY") instead of the unexpected token.

The format corpus records the old layout as previously_formatted, and
one SQL Server departure is dropped because SQLQuack now matches
SQLClean's recorded layout.

kvlonge commented Nov 12, 2026

Copy link
Copy Markdown
Contributor Author

Changed format corpus cases

Every formatted value changed in crates/sqlclean/src/sql_lint/tests/fixtures/format_corpus.json, at SQLQuack 8d28aa4c. Each new value equals SQLQuack 0.6.0's own corpus entry. The corpus lists the GAP_FILL … ORDER BY time query in item 6 twice; both entries changed the same way (49 entries, 48 queries).

1. bigquery: SELECT x, 1 AS y GROUP BY 1 ORDER BY 1

Before

SELECT
  x,
  1 AS y
GROUP BY
  1
ORDER BY
  1

After

SELECT
  x,
  1 AS y
GROUP BY 1
ORDER BY 1
2. bigquery: SELECT y + 1 FROM x GROUP BY y + 1 ORDER BY 1

Before

SELECT y + 1
FROM x
GROUP BY
  y + 1
ORDER BY
  1

After

SELECT y + 1
FROM x
GROUP BY y + 1
ORDER BY 1
3. bigquery: SELECT ARRAY((SELECT AS STRUCT 1 AS a, 2 AS b))

Before

SELECT
  ARRAY((
    SELECT AS STRUCT
      1 AS a,
      2 AS b
  ))

After

SELECT
  ARRAY(
    (
      SELECT AS STRUCT
        1 AS a,
        2 AS b
    )
  )
4. bigquery: SELECT ARRAY((SELECT AS STRUCT 1 AS a, 2 AS b) LIMIT 10)

Before

SELECT
  ARRAY((
    SELECT AS STRUCT
      1 AS a,
      2 AS b
  ) LIMIT 10)

After

SELECT
  ARRAY(
    (
      SELECT AS STRUCT
        1 AS a,
        2 AS b
    ) LIMIT 10
  )
5. bigquery: SELECT y + 1 z FROM x GROUP BY y + 1 ORDER BY z

Before

SELECT y + 1 z
FROM x
GROUP BY
  y + 1
ORDER BY
  z

After

SELECT y + 1 z
FROM x
GROUP BY y + 1
ORDER BY z
6. bigquery: SELECT y + 1 z FROM x GROUP BY y + 1

Before

SELECT y + 1 z
FROM x
GROUP BY
  y + 1

After

SELECT y + 1 z
FROM x
GROUP BY y + 1
7. bigquery: SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_c…

Before

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'locf')]
)
ORDER BY
  time

After

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'locf')]
)
ORDER BY time
8. bigquery: SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_c…

Before

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'linear')],
  ignore_null_values => FALSE
)
ORDER BY
  time

After

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'linear')],
  ignore_null_values => FALSE
)
ORDER BY time
9. bigquery: SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE) ORDER B…

Before

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE
)
ORDER BY
  time

After

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE
)
ORDER BY time
10. bigquery: SELECT * FROM GAP_FILL(TABLE device_data, ts_column => 'time', bucket_width => INTERVAL '1' MINUTE, value_c…

Before

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'null')],
  origin => CAST('2023-11-01 09:30:01' AS DATETIME)
)
ORDER BY
  time

After

SELECT *
FROM GAP_FILL(
  TABLE device_data,
  ts_column => 'time',
  bucket_width => INTERVAL '1' MINUTE,
  value_columns => [('signal', 'null')],
  origin => CAST('2023-11-01 09:30:01' AS DATETIME)
)
ORDER BY time
11. bigquery: SELECT * FROM ML.PREDICT(MODEL mydataset.mymodel, (SELECT label, column1, column2 FROM mydataset.mytable))

Before

SELECT *
FROM ML.PREDICT(MODEL mydataset.mymodel, (
  SELECT
    label,
    column1,
    column2
  FROM mydataset.mytable
))

After

SELECT *
FROM ML.PREDICT(
  MODEL mydataset.mymodel,
  (
    SELECT
      label,
      column1,
      column2
    FROM mydataset.mytable
  )
)
12. bigquery: SELECT * FROM ML.PREDICT(MODEL `my_project`.my_dataset.my_model, (SELECT * FROM input_data))

Before

SELECT *
FROM ML.PREDICT(MODEL `my_project`.my_dataset.my_model, (
  SELECT *
  FROM input_data
))

After

SELECT *
FROM ML.PREDICT(
  MODEL `my_project`.my_dataset.my_model,
  (
    SELECT *
    FROM input_data
  )
)
13. bigquery: SELECT * FROM ML.FEATURES_AT_TIME((SELECT 1), num_rows => 1)

Before

SELECT *
FROM ML.FEATURES_AT_TIME((
  SELECT 1
), num_rows => 1)

After

SELECT *
FROM ML.FEATURES_AT_TIME(
  (
    SELECT 1
  ),
  num_rows => 1
)
14. bigquery: SELECT * FROM ML.FORECAST(MODEL `mydataset.mymodel`, (SELECT * FROM mydataset.query_table), STRUCT())

Before

SELECT *
FROM ML.FORECAST(MODEL `mydataset.mymodel`, (
  SELECT *
  FROM mydataset.query_table
), STRUCT())

After

SELECT *
FROM ML.FORECAST(
  MODEL `mydataset.mymodel`,
  (
    SELECT *
    FROM mydataset.query_table
  ),
  STRUCT()
)
15. bigquery: SELECT JSON_OBJECT(['a', 'b'], [STRUCT(10 AS id, 'Red' AS color), STRUCT(20 AS id, 'Blue' AS color)]) AS js…

Before

SELECT
  JSON_OBJECT(['a', 'b'], [
    STRUCT(10 AS id, 'Red' AS color),
    STRUCT(20 AS id, 'Blue' AS color)
  ]) AS json_data

After

SELECT
  JSON_OBJECT(
    ['a', 'b'],
    [
      STRUCT(10 AS id, 'Red' AS color),
      STRUCT(20 AS id, 'Blue' AS color)
    ]
  ) AS json_data
16. bigquery: SELECT MOD((SELECT 1), 2)

Before

SELECT
  MOD((
    SELECT 1
  ), 2)

After

SELECT
  MOD(
    (
      SELECT 1
    ),
    2
  )
17. bigquery: SELECT a, GROUP_CONCAT(b) FROM table GROUP BY a

Before

SELECT
  a,
  GROUP_CONCAT(b)
FROM table
GROUP BY
  a

After

SELECT
  a,
  GROUP_CONCAT(b)
FROM table
GROUP BY a
18. databricks: SELECT col1 FROM t CLUSTER BY col1

Before

SELECT col1
FROM t
CLUSTER BY
  col1

After

SELECT col1
FROM t
CLUSTER BY col1
19. databricks: SELECT test, LISTAGG(email, '') AS Email FROM organizations GROUP BY test

Before

SELECT
  test,
  LISTAGG(email, '') AS Email
FROM organizations
GROUP BY
  test

After

SELECT
  test,
  LISTAGG(email, '') AS Email
FROM organizations
GROUP BY test
20. duckdb: SELECT * FROM t1 AS a AT (VERSION => 3) JOIN t2 AS b AT (TIMESTAMP => NOW() - INTERVAL '1' WEEK) ON a.id = …

Before

SELECT *
FROM t1 AS a AT (VERSION => 3)
JOIN t2 AS b AT (TIMESTAMP => NOW() - INTERVAL '1' WEEK)
  ON a.id = b.id

After

SELECT *
FROM t1 AS a AT (VERSION => 3)
JOIN t2 AS b AT (TIMESTAMP => NOW() - INTERVAL '1' WEEK) ON a.id = b.id
21. duckdb: SELECT * FROM foo ASOF LEFT JOIN bar ON a = b

Before

SELECT *
FROM foo
ASOF LEFT JOIN bar
  ON a = b

After

SELECT *
FROM foo
ASOF LEFT JOIN bar ON a = b
22. duckdb: SELECT a, LOGICAL_OR(b) FROM foo GROUP BY a

Before

SELECT
  a,
  LOGICAL_OR(b)
FROM foo
GROUP BY
  a

After

SELECT
  a,
  LOGICAL_OR(b)
FROM foo
GROUP BY a
23. duckdb: SELECT i FROM RANGE(5) AS _(i) ORDER BY i ASC

Before

SELECT i
FROM RANGE(5) AS _(i)
ORDER BY
  i ASC

After

SELECT i
FROM RANGE(5) AS _(i)
ORDER BY i ASC
24. duckdb: SELECT i FROM GENERATE_SERIES(12) AS _(i) ORDER BY i ASC

Before

SELECT i
FROM GENERATE_SERIES(12) AS _(i)
ORDER BY
  i ASC

After

SELECT i
FROM GENERATE_SERIES(12) AS _(i)
ORDER BY i ASC
25. duckdb: SELECT l_returnflag, l_linestatus, sum_qty: sum(l_quantity), sum_base_price: sum(l_extendedprice), sum_disc…

Before

SELECT
  l_returnflag,
  l_linestatus,
  sum_qty:sum(l_quantity),
  sum_base_price:sum(l_extendedprice),
  sum_disc_price:sum(l_extendedprice
  * (
    1 - l_discount
  )),
  sum_charge:sum(l_extendedprice
  * (
    1 - l_discount
  )
  * (
    1 + l_tax
  )),
  avg_qty:avg(l_quantity),
  avg_price:avg(l_extendedprice),
  avg_disc:avg(l_discount),
  count_order:count(*)

After

SELECT
  l_returnflag,
  l_linestatus,
  sum_qty:sum(l_quantity),
  sum_base_price:sum(l_extendedprice),
  sum_disc_price:sum(
    l_extendedprice
    * (
      1 - l_discount
    )
  ),
  sum_charge:sum(
    l_extendedprice
    * (
      1 - l_discount
    )
    * (
      1 + l_tax
    )
  ),
  avg_qty:avg(l_quantity),
  avg_price:avg(l_extendedprice),
  avg_disc:avg(l_discount),
  count_order:count(*)
26. postgres: SELECT SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - SU…

Before

SELECT SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) - SUM(x) OVER (PARTITION BY y ORDER BY interval ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS total

After

SELECT
  SUM(x) OVER (
    PARTITION BY y
    ORDER BY interval
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  )
  - SUM(x) OVER (
    PARTITION BY y
    ORDER BY interval
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) AS total
27. postgres: SELECT c.oid, n.nspname, c.relname FROM pg_catalog.pg_class AS c LEFT JOIN pg_catalog.pg_namespace AS n ON …

Before

SELECT
  c.oid,
  n.nspname,
  c.relname
FROM pg_catalog.pg_class AS c
LEFT JOIN pg_catalog.pg_namespace AS n
  ON n.oid = c.relnamespace
WHERE
  c.relname OPERATOR(pg_catalog.~) '^(courses)$' COLLATE pg_catalog.default AND pg_catalog.PG_TABLE_IS_VISIBLE(c.oid)
ORDER BY
  2,
  3

After

SELECT
  c.oid,
  n.nspname,
  c.relname
FROM pg_catalog.pg_class AS c
LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.relnamespace
WHERE
  c.relname OPERATOR(pg_catalog.~) '^(courses)$' COLLATE pg_catalog.default AND pg_catalog.PG_TABLE_IS_VISIBLE(c.oid)
ORDER BY
  2,
  3
28. postgres: SELECT m.name FROM manufacturers AS m LEFT JOIN LATERAL GET_PRODUCT_NAMES(m.id) pname ON TRUE WHERE pname I…

Before

SELECT m.name
FROM manufacturers AS m
LEFT JOIN LATERAL GET_PRODUCT_NAMES(m.id) pname
  ON TRUE
WHERE pname IS NULL

After

SELECT m.name
FROM manufacturers AS m
LEFT JOIN LATERAL GET_PRODUCT_NAMES(m.id) pname ON TRUE
WHERE pname IS NULL
29. postgres: SELECT 1 FROM ((VALUES (1)) AS vals(id) LEFT OUTER JOIN tbl ON vals.id = tbl.id)

Before

SELECT 1
FROM ((VALUES (1)) AS vals(id)
LEFT OUTER JOIN tbl
  ON vals.id = tbl.id)

After

SELECT 1
FROM (
  (VALUES (1)) AS vals(id)
  LEFT OUTER JOIN tbl ON vals.id = tbl.id
)
30. postgres: SELECT TRIM(ARRAY_TO_STRING(ARRAY(SELECT val FROM UNNEST(ARRAY['a', 'b']) WITH ORDINALITY AS u(val, rn)), '…

Before

SELECT
  TRIM(ARRAY_TO_STRING(
    ARRAY(
      SELECT val
      FROM UNNEST(ARRAY['a', 'b']) WITH ORDINALITY AS u(val, rn)
    ),
    ' '
  ))

After

SELECT
  TRIM(
    ARRAY_TO_STRING(
      ARRAY(
        SELECT val
        FROM UNNEST(ARRAY['a', 'b']) WITH ORDINALITY AS u(val, rn)
      ),
      ' '
    )
  )
31. snowflake: SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN ('2023_Q1', '2023_Q2', '2023_Q3', '2023_Q4',…

Before

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN ('2023_Q1', '2023_Q2', '2023_Q3', '2023_Q4', '2024_Q1') DEFAULT ON NULL (0))
ORDER BY
  empid

After

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN ('2023_Q1', '2023_Q2', '2023_Q3', '2023_Q4', '2024_Q1') DEFAULT ON NULL (0))
ORDER BY empid
32. snowflake: SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (SELECT DISTINCT quarter FROM ad_campaign_ty…

Before

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (SELECT DISTINCT
  quarter
FROM ad_campaign_types_by_quarter
WHERE television = TRUE
ORDER BY
  quarter))
ORDER BY
  empid

After

SELECT *
FROM quarterly_sales PIVOT(
  SUM(amount) FOR quarter IN (
    SELECT DISTINCT
      quarter
    FROM ad_campaign_types_by_quarter
    WHERE television = TRUE
    ORDER BY quarter
  )
)
ORDER BY empid
33. snowflake: SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY ORDER BY quarter)) ORDER BY empid

Before

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY ORDER BY quarter))
ORDER BY
  empid

After

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY ORDER BY quarter))
ORDER BY empid
34. snowflake: SELECT * FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY)) ORDER BY empid

Before

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY))
ORDER BY
  empid

After

SELECT *
FROM quarterly_sales PIVOT(SUM(amount) FOR quarter IN (ANY))
ORDER BY empid
35. snowflake: SELECT * FROM DATA AS DATA_L ASOF JOIN DATA AS DATA_R MATCH_CONDITION (DATA_L.VAL > DATA_R.VAL) ON DATA_L.I…

Before

SELECT *
FROM DATA AS DATA_L
ASOF JOIN DATA AS DATA_R MATCH_CONDITION (DATA_L.VAL > DATA_R.VAL)
  ON DATA_L.ID = DATA_R.ID

After

SELECT *
FROM DATA AS DATA_L
ASOF JOIN DATA AS DATA_R MATCH_CONDITION (DATA_L.VAL > DATA_R.VAL) ON DATA_L.ID = DATA_R.ID
36. snowflake: SELECT * FROM t1 INNER JOIN t2 USING (t1.col)

Before

SELECT *
FROM t1
INNER JOIN t2
  USING (t1.col)

After

SELECT *
FROM t1
INNER JOIN t2 USING (t1.col)
37. snowflake: SELECT * FROM (SELECT * FROM t1 JOIN t2 ON t1.a = t2.c) TABLESAMPLE BERNOULLI (1)

Before

SELECT *
FROM (
  SELECT *
  FROM t1
  JOIN t2
    ON t1.a = t2.c
) TABLESAMPLE BERNOULLI (1)

After

SELECT *
FROM (
  SELECT *
  FROM t1
  JOIN t2 ON t1.a = t2.c
) TABLESAMPLE BERNOULLI (1)
38. snowflake: SELECT oldt.* , newt.* FROM my_table BEFORE(STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt FU…

Before

SELECT
  oldt.*,
  newt.*
FROM my_table BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt
FULL OUTER JOIN my_table AT (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS newt
  ON oldt.id = newt.id
WHERE
  oldt.id IS NULL
  OR newt.id IS NULL;

After

SELECT
  oldt.*,
  newt.*
FROM my_table BEFORE (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS oldt
FULL OUTER JOIN my_table AT (STATEMENT => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726') AS newt ON oldt.id = newt.id
WHERE
  oldt.id IS NULL
  OR newt.id IS NULL;
39. snowflake: SELECT * FROM (TABLE('t1') CROSS JOIN TABLE('t2'))

Before

SELECT *
FROM (TABLE('t1')
CROSS JOIN TABLE('t2'))

After

SELECT *
FROM (
  TABLE('t1')
  CROSS JOIN TABLE('t2')
)
40. snowflake: SELECT value FROM TABLE(FLATTEN(input => SELECT PARSE_JSON('[1, 2]')))

Before

SELECT value
FROM TABLE(FLATTEN(input => SELECT
  PARSE_JSON('[1, 2]')))

After

SELECT value
FROM TABLE(
  FLATTEN(
    input => SELECT
      PARSE_JSON('[1, 2]')
  )
)
41. snowflake: SELECT * FROM TABLE(model_trained_with_labeled_data!DETECT_ANOMALIES(INPUT_DATA => TABLE(view_with_data_to_…

Before

SELECT *
FROM TABLE(model_trained_with_labeled_data!DETECT_ANOMALIES(
  INPUT_DATA => TABLE(view_with_data_to_analyze),
  TIMESTAMP_COLNAME => 'date',
  TARGET_COLNAME => 'sales',
  CONFIG_OBJECT => OBJECT_CONSTRUCT('prediction_interval', 0.99)
))

After

SELECT *
FROM TABLE(
  model_trained_with_labeled_data!DETECT_ANOMALIES(
    INPUT_DATA => TABLE(view_with_data_to_analyze),
    TIMESTAMP_COLNAME => 'date',
    TARGET_COLNAME => 'sales',
    CONFIG_OBJECT => OBJECT_CONSTRUCT('prediction_interval', 0.99)
  )
)
42. snowflake: SELECT * FROM a CROSS DIRECTED JOIN b USING (id)

Before

SELECT *
FROM a
CROSS DIRECTED JOIN b
  USING (id)

After

SELECT *
FROM a
CROSS DIRECTED JOIN b USING (id)
43. snowflake: SELECT * FROM a INNER DIRECTED JOIN b USING (id)

Before

SELECT *
FROM a
INNER DIRECTED JOIN b
  USING (id)

After

SELECT *
FROM a
INNER DIRECTED JOIN b USING (id)
44. snowflake: SELECT * FROM a NATURAL INNER DIRECTED JOIN b USING (id)

Before

SELECT *
FROM a
NATURAL INNER DIRECTED JOIN b
  USING (id)

After

SELECT *
FROM a
NATURAL INNER DIRECTED JOIN b USING (id)
45. tsql: SELECT * FROM taxi ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY

Before

SELECT *
FROM taxi
ORDER BY
  1
OFFSET 0 ROWS
FETCH NEXT 3 ROWS ONLY

After

SELECT *
FROM taxi
ORDER BY 1
OFFSET 0 ROWS
FETCH NEXT 3 ROWS ONLY
46. tsql: SELECT x FROM @MyTableVar AS m JOIN Employee ON m.EmployeeID = Employee.EmployeeID

Before

SELECT x
FROM @MyTableVar AS m
JOIN Employee
  ON m.EmployeeID = Employee.EmployeeID

After

SELECT x
FROM @MyTableVar AS m
JOIN Employee ON m.EmployeeID = Employee.EmployeeID
47. tsql: SELECT (SELECT TOP 5 a, b FROM t ORDER BY b DESC FOR JSON PATH) AS j FROM x

Before

SELECT
  (
    SELECT TOP 5
      a,
      b
    FROM t
    ORDER BY
      b DESC
    FOR JSON
      PATH
  ) AS j
FROM x

After

SELECT
  (
    SELECT TOP 5
      a,
      b
    FROM t
    ORDER BY b DESC
    FOR JSON
      PATH
  ) AS j
FROM x
48. tsql: SELECT x FROM a INNER LOOP JOIN b ON b.id = a.id

Before

SELECT x
FROM a
INNER LOOP JOIN b
  ON b.id = a.id

After

SELECT x
FROM a
INNER LOOP JOIN b ON b.id = a.id

Departures (sqlquack_departures.json): tsql … UNION ALL SELECT TOP 10 a FROM t2 ORDER BY a DESC removed (SQLQuack now matches the recorded layout); tsql SELECT COUNT(*) FROM (… ORDER BY a DESC) AS t and bigquery SELECT STRUCT<STRING>((SELECT 'foo')).* updated.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Deploying sqlclean with  Cloudflare Pages  Cloudflare Pages

Latest commit: d9ab6d4
Status: ✅  Deploy successful!
Preview URL: https://e4035242.sqlclean.pages.dev
Branch Preview URL: https://wip-pin-sqlquack-060.sqlclean.pages.dev

View logs

@kvlonge
kvlonge merged commit 293cacd into main Nov 12, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant