Skip to content

feat: format and lint with SQLQuack instead of Polyglot - #38

Merged
kvlonge merged 26 commits into
mainfrom
feat/sqlquack-native-format
Nov 8, 2026
Merged

kvlonge merged 26 commits into
mainfrom
feat/sqlquack-native-format

Conversation

@kvlonge

@kvlonge kvlonge commented Nov 8, 2026

Copy link
Copy Markdown
Contributor

Why

SQLClean formatted and linted through Polyglot. SQLQuack now has native parsers and a lossless formatter, so SQLClean can drop Polyglot entirely.

Changes

  • Formatting goes through sqlquack-format for Snowflake, DuckDB and T-SQL. Other dialects are reported unavailable and left as written.
  • Lint and file facts use SQLQuack's native trees, with DROP and utility statement kinds and the joins Polyglot reported.
  • A file with a leading UTF-8 byte order mark is read without it and written back with it.
  • Every SQLQuack crate is pinned to main edfa30b2. Polyglot is no longer a dependency.
  • The format corpus gates T-SQL at 52 formatted and 23 refused, with SQLQuack's named departures.

Verification

  • make check-ci: ruff, ty, fensu (0 faults), docs skill, fmt and clippy pass.
  • cargo test --all and uv run pytest tests (67) pass, including the format corpus for Snowflake, DuckDB and T-SQL and the fingerprint goldens.
  • cargo tree -i polyglot_sql matches no package, and Cargo.lock has no polyglot entries.
  • Native lint runs on postgres, generic and sqlite.

Snowflake and DuckDB requests now go to sqlquack-format, pinned by git
revision, which lays out SQL on SQLQuack's own tokenizer and parser and
checks token identity and idempotency itself. Other dialects still use
the Polyglot layout oracle.

Work in progress: 6 formatter tests still expect Polyglot's layout.
… the Polyglot AST API

SQLClean's corpus check now expects SQLQuack's named departures, listed
with their reasons in sqlquack_departures.json: 15 DuckDB and 13
Snowflake queries that SQLQuack lays out or refuses differently. Called
names fold to upper case as SQLBuild's query fingerprint folds them,
except in unaliased Snowflake select items, whose text names their
column. The re-print count test runs on PostgreSQL, which still uses
the Polyglot layout.

RuleContext.polyglot_ast() and the ast extra are removed.

Work in progress: CTE macro placeholders are not laid out yet.
…Polyglot

The lint entry points (lint, lint_json, lint_batch_json, parse_sql,
catalogue, default_rules, cross_join_subject, fill_finding_path and
validate_dialect) now call sqlquack-lint, the port of this lint engine
and its quality Rules onto SQLQuack's tokens and trees. The lint request,
response and parse types are SQLQuack's, named in sql_lint::types; the
format types stay in sql_lint::models. The placeholder constants take
sqlquack-lint's values. sql_quality is removed: its Rules run inside
sqlquack-lint.

sql_facts reads SQLQuack's tokens and trees through parse_sql, with the
same collection code.

Lint for a dialect SQLQuack does not parse yet, such as T-SQL or MySQL,
answers that lint is unavailable rather than reporting findings, so the
T-SQL bare UNION test is ignored until that parser lands.

The formatter is unchanged and still reads Polyglot; the one lint helper
it used, token_depths, stays in sql_lint/_helpers/engine.rs.

SQLQuack is pinned at wip/lint-port 4f9ac36d8.
SQLQuack's Snowflake parse keeps DATEPART, LAST_DAY and DATE_DIFF as the
calls written, so their bare date part, such as day in DATEPART(day, ts),
parsed as a column, where Polyglot lowered the call and read it as a unit.
Statement facts now leave that argument out of the columns: any bare name
for DATEPART and LAST_DAY, and only a known unit name for DATE_DIFF, as
Polyglot did.

Against SQLClean on Polyglot, the Snowflake facts of 12,313 inputs both
parse now differ on 6, all JSON_OBJECTAGG(t.x), whose argument SQLQuack
reports as a column and Polyglot dropped; DuckDB's 5,352 are identical.
…ot parse

file_facts now answers a dialect SQLQuack does not parse yet, such as
MySQL, with no statements and an unavailable reason, the way lint's
response does, instead of an error; an unknown dialect is still an error.
statement_ends answers None for such a dialect. Both ask lint whether the
dialect is unavailable, so facts and lint always agree.

The CLI keeps failing those files for now: the custom Rule host turns an
unavailable reason into its error, and format_source turns None into one,
until the per-file unavailable report replaces them.
… Postgres lint slice

SQLQuack is now pinned at the lint slice that parses PostgreSQL and
Generic natively (wip/lint-postgres bd308904a), so their lint and facts
are available.

The native PostgreSQL parser folds unquoted names to lower case, so the
facts read table, column, CTE and alias names from the source as written.
A column's span now starts at its qualifier when the parser located the
name alone, as for a subscripted column. The CREATE and ALTER statements
the native tree keeps as a generic object take the kind Polyglot's tree
gave them, such as create_index or alter_table.

The Rule test harness fails a case whose dialect has no facts, rather
than evaluating the Rule over an empty file.
Dialects SQLQuack fingerprints use its shared function; the others keep the
Polyglot token stream SQLBuild also uses for them. A comma-less run of CTE
macro placeholders is now separated by blank lines like any other CTE.
…atter, and drop the Polyglot oracle

Dialects without a native SQLQuack formatter now return their SQL unchanged
with an unavailable reason instead of a Polyglot layout, so the oracle
formatter and its token layout, printed layout and syntax protection are
removed. Fingerprints use SQLQuack's generic tokens for dialects without
their own tokenizer, as SQLBuild does; the SQL Server golden changes to the
generic digest. The recording demo's custom Rule reads statement facts
instead of the removed AST.
…f failing

check lists each such file once as a warning, and as unavailable in JSON and
SARIF, without counting it as a problem or running custom Rules on it. format
and fix leave the file unchanged and do not fail.
… and move the format cases beside the layout rules
Maps the native utility_statement and drop_object kinds back to the
statement kinds SQLClean reported before (describe, set_statement,
transaction, drop_table and so on). Pins SQLQuack to the lint-postgres
landing head provisionally; the shared rev is still to be agreed.
Editors such as SSMS save SQL with a UTF-8 byte order mark. check, format and
fix now read the SQL after it, so every dialect lints and formats the file,
and format and fix write the mark back unchanged. Not yet built or tested.
The statement-kind mapping builds on the shared SQLQuack rev already
pinned here, so the provisional pin bump is dropped.
Not releasable: 19c51fa2 is not on SQLQuack main yet. Two test updates follow
SQLQuack's Snowflake grammar checks: the format corpus expects UNNEST refused
(Snowflake has no UNNEST), and the no-harness empty-fixture case calls the
relation through TABLE( ) as Snowflake requires.
SQLQuack's FormatOptions now takes a FormatDialect; SQLClean keeps its syntax dialect for the CTE macro pass and maps it for the layout.
T-SQL requests go to SQLQuack's SQL Server formatter; the CTE macro pass, which needs the shared tokenizer, is skipped, so a placeholder SQLQuack cannot parse is refused. The corpus test gates T-SQL at 52 formatted and 23 refused, with SQLQuack's named departures.
The native tree keeps a comma relation that follows an explicit join in
the FROM list, and a join nested by its ON clauses inside the outer join's
relation, so facts lost both. A comma relation after the first explicit
join is now an IMPLICIT join, and the joins of an unparenthesized nested
join are reported, with the outer join named by its first table. A
parenthesized join group still reports only the join to it, as before.
Every SQLQuack crate resolves to the one main commit edfa30b26a0bab86661f96490b86a4f24b92db0a.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying sqlclean with  Cloudflare Pages  Cloudflare Pages

Latest commit: 3020a07
Status: ✅  Deploy successful!
Preview URL: https://39d00f6e.sqlclean.pages.dev
Branch Preview URL: https://feat-sqlquack-native-format.sqlclean.pages.dev

View logs

@kvlonge
kvlonge merged commit 6c0d395 into main Nov 8, 2026
35 of 40 checks passed
This was referenced Nov 8, 2026
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