Skip to content

fix: keep authored SQL tokens when formatting - #846

Merged
kvlonge merged 1 commit into
mainfrom
fix/formatter-preserve-function-names
Sep 26, 2026
Merged

kvlonge merged 1 commit into
mainfrom
fix/formatter-preserve-function-names

Conversation

@kvlonge

@kvlonge kvlonge commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Why

sqb format regenerated SQL from the parse tree, so canonical spellings from the SQL library were written back into user files. In Snowflake it renamed STARTSWITH to STARTS_WITH, which Snowflake rejects with "Unknown function", and rewrote SUBSTR, TIMESTAMPDIFF and TRY_TO_DECIMAL to synonyms. The formatter's syntax-tree check could not see this, because both spellings parse to the same node.

Changes

  • After laying out SQL, align the authored and generated token streams, and write the authored text of every aligned token back into the new layout.
  • Only whitespace, the case of unquoted words, an added AS, and dropped optional terminators may differ. Any other structural difference refuses the format and leaves the file unchanged with a named reason. The idempotence pass uses the same check.
  • Native unit tests cover the renamed functions, swapped arguments and the refusal paths. CLI integration tests cover Snowflake STARTSWITH/SUBSTR/TIMESTAMPDIFF/TRY_TO_DECIMAL and DuckDB IFNULL/!=, and all of them fail on the previous formatter.

Known limitation, tracked in CHI-402: PostgreSQL substring(a, 1, 2) and SELECT ALL are now refused rather than restructured.

Verification

  • make test: 7,794 passed.
  • make check-ci: passed, including clippy on Rust 1.98.
  • On a 1,337-file Snowflake project: 588 files formatted, 0 function renames (previously 82), 0 new refusals.
  • One independent review: no meaning-changing output found. The finding (refusals above) is accepted and tracked.

sqb format regenerated SQL from the parse tree, so any spelling the SQL
library canonicalised was written back into the user's file. In Snowflake
that renamed STARTSWITH to STARTS_WITH, which Snowflake rejects, and also
rewrote SUBSTR, TIMESTAMPDIFF and TRY_TO_DECIMAL to synonyms. The formatter's
syntax-tree check could not see this because both spellings parse to the same
node.

After generating the layout, walk the authored and generated token streams
together and write the authored text of every aligned token into the new
layout. Only whitespace, the case of unquoted words, an added AS, and dropped
optional terminators may differ; any other structural difference refuses the
format and leaves the file unchanged. The same check runs on the idempotence
pass.

On a 1,337-file Snowflake project this removes all 82 function renames, with
no new refusals.
@kvlonge
kvlonge enabled auto-merge (squash) September 26, 2026 19:33
@kvlonge
kvlonge merged commit 0eb4bb7 into main Sep 26, 2026
28 checks passed
@kvlonge
kvlonge deleted the fix/formatter-preserve-function-names branch September 26, 2026 19:46
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