Skip to content

Fix four expression-formatting correctness bugs#37

Merged
gmr merged 2 commits into
mainfrom
fix/expr-correctness
Jul 6, 2026
Merged

Fix four expression-formatting correctness bugs#37
gmr merged 2 commits into
mainfrom
fix/expr-correctness

Conversation

@gmr

@gmr gmr commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes four correctness bugs in src/formatter/expr.rs, three of which silently changed SQL semantics.

Problem

Solution

Adds river regression fixtures for each fix and two CAST parenthesization tests in test_parens.rs. Full suite (71 fixtures + unit/doc tests) is green; just check passes.

Fixes #22
Fixes #23
Fixes #24
Fixes #25

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved SQL formatting to preserve PostgreSQL semantics for parenthesized field access and compound operands under :: typecasts.
    • Refined qualified-name rendering to collapse double dots only when safe (outside quoted identifiers).
    • Enhanced whitespace collapsing to respect quoted/escaped string content, avoiding unintended changes to escape string literals.
  • Tests
    • Added/updated fixtures covering cast parenthesis correctness, field selection after parenthesized expressions, escaped string literals, and quoted identifiers containing double dots.

- #22: collapse_whitespace_outside_quotes now honors backslash escaping
  inside E'...' escape strings, so \' is not misread as the terminator
  and interior whitespace is preserved.
- #23: format_qualified_name collapses double dots only outside quoted
  identifiers, so "a..b" keeps both dots instead of becoming "a.b".
- #24: the CAST-to-:: rewrite parenthesizes any compound operand (one
  with a top-level operator) even when it also contains a function call,
  so CAST(foo(x) + 1 AS integer) becomes (foo(x) + 1)::INTEGER rather
  than foo(x) + 1::INTEGER. Bare function calls stay unparenthesized.
- #25: field selection on a parenthesized base keeps its parens and drops
  the stray space, so (foo).bar formats as (foo).bar, preserving the
  composite-field meaning.

Adds river regression fixtures for each fix and two CAST parenthesization
tests in test_parens.rs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e56038d-0ad8-4055-a24e-9a86f966f74e

📥 Commits

Reviewing files that changed from the base of the PR and between 48b56e0 and fbcc514.

📒 Files selected for processing (2)
  • src/formatter/expr.rs
  • tests/test_parens.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/formatter/expr.rs

📝 Walkthrough

Walkthrough

This PR updates SQL expression formatting to preserve parentheses and quoting semantics for field selection, CAST-to-:: rewriting, qualified identifiers with consecutive dots, and PostgreSQL escape strings. It also adds regression fixtures and tests for each case.

Changes

Expression formatter edge-case fixes

Layer / File(s) Summary
Parenthesized field selection formatting
src/formatter/expr.rs, tests/fixtures/river/expr_paren_field_selection.*, tests/test_parens.rs
format_c_expr now joins .field selectors without a space and re-wraps stripped bases as (base).bar when needed.
CAST-to-:: parenthesization fix
src/formatter/expr.rs, tests/fixtures/river/expr_cast_compound_func_parens.*, tests/test_parens.rs, tests/fixtures/river/expr_escape_string_whitespace.*
format_func_expr_common uses has_top_level_space to decide when CAST operands need parentheses, with coverage for compound expressions, simple function calls, and escape-string literals.
Qualified-name double-dot collapsing fix
src/formatter/expr.rs, tests/fixtures/river/expr_quoted_identifier_double_dots.*
format_qualified_name now collapses double dots only outside quoted identifiers.
Escape string whitespace collapsing fix
src/formatter/expr.rs, tests/fixtures/river/expr_escape_string_whitespace.*
collapse_whitespace_outside_quotes now recognizes E'...' escape strings and backslash escapes so literal whitespace is preserved.
Helper updates
src/formatter/expr.rs
New helper logic tracks top-level whitespace, quoted regions, and escape-string state for the formatter paths above.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • gmr/libpgfmt#3: Related src/formatter/expr.rs parenthesization and field-selection formatting changes.
  • gmr/libpgfmt#4: Related CAST(...) to :: rewriting and expression-formatting edge cases in the same formatter area.

Poem

A rabbit hopped through SQL night,
To keep the quotes and dots just right.
(foo).bar now stays intact,
And E'it\'s here' won’t be hacked.
With ::INTEGER, snug and neat,
The formatter’s pounce is now complete.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: four expression-formatting correctness fixes.
Linked Issues check ✅ Passed The PR implements all four linked fixes and adds regression coverage for each one.
Out of Scope Changes check ✅ Passed All code and test changes are directly related to the four formatter bugs and their regressions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/formatter/expr.rs`:
- Around line 1466-1495: has_top_level_space does not preserve backslash-escape
context for E'...' string literals, which can misclassify CAST expressions like
E'a\'s value'::text as compound. Update has_top_level_space in expr.rs to mirror
the escape-string handling used by collapse_whitespace_outside_quotes, so
escaped quotes inside E-prefixed literals are tracked correctly and top-level
whitespace detection stays accurate.
- Around line 416-429: The field-selection handling in formatter::expr::format
needs to distinguish function-call results from already-parenthesized
expressions, because checking last.contains('(') is too broad. Update the logic
around the formatted.starts_with('.') branch so it re-wraps any base expression
that is not explicitly enclosed in outer parentheses, ensuring cases like
foo(x).bar become (foo(x)).bar while preserving existing parentheses for other
expressions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6a07a4af-48fa-4531-92c5-4842fbffd11e

📥 Commits

Reviewing files that changed from the base of the PR and between c949e2b and 48b56e0.

📒 Files selected for processing (10)
  • src/formatter/expr.rs
  • tests/fixtures/river/expr_cast_compound_func_parens.expected
  • tests/fixtures/river/expr_cast_compound_func_parens.sql
  • tests/fixtures/river/expr_escape_string_whitespace.expected
  • tests/fixtures/river/expr_escape_string_whitespace.sql
  • tests/fixtures/river/expr_paren_field_selection.expected
  • tests/fixtures/river/expr_paren_field_selection.sql
  • tests/fixtures/river/expr_quoted_identifier_double_dots.expected
  • tests/fixtures/river/expr_quoted_identifier_double_dots.sql
  • tests/test_parens.rs

Comment thread src/formatter/expr.rs
Comment thread src/formatter/expr.rs
- Field selection on a function-call result now stays parenthesized.
  The old `contains('(')` guard skipped the re-wrap for `(foo(x)).bar`
  because the base already held a paren, collapsing it to the invalid
  `foo(x).bar`. Re-wrap now keys off outer enclosure instead.

- has_top_level_space now tracks E'...' backslash escapes and doubled
  '' quotes, mirroring collapse_whitespace_outside_quotes. Previously a
  CAST like `CAST(E'a\'s value' AS text)` was misread as a compound
  operand and wrapped in needless parens.

Both paths gain regression tests in tests/test_parens.rs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gmr gmr merged commit e2deaa1 into main Jul 6, 2026
3 checks passed
@gmr gmr deleted the fix/expr-correctness branch July 6, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant