Skip to content

Emit comma between CTEs in non-compact WITH formatting#7

Open
sean-nzl wants to merge 1 commit intogmr:mainfrom
sean-nzl:fix-multi-cte-comma
Open

Emit comma between CTEs in non-compact WITH formatting#7
sean-nzl wants to merge 1 commit intogmr:mainfrom
sean-nzl:fix-multi-cte-comma

Conversation

@sean-nzl
Copy link
Copy Markdown

@sean-nzl sean-nzl commented May 6, 2026

Summary

  • format_with_clause_left was unconditionally pushing ) after each CTE body, dropping the comma separator between CTEs whenever compact_ctes was off (most visibly in the dbt style).
  • Closing line is now ), for non-final CTEs and ) for the last, matching the comma handling already present in the compact-CTE branch.
  • Added a minimal multi-CTE fixture for the dbt style as a regression test.

Repro (before fix)

Input:

WITH a AS (SELECT 1 AS x), b AS (SELECT 2 AS y) SELECT * FROM a, b

Formatting with Style::Dbt produced )\nb as ( (no comma) — invalid SQL.

Test plan

  • cargo test (73 pass; new dbt_select_cte_multi)
  • cargo clippy --all-targets -- -D warnings
  • cargo fmt --check

Summary by CodeRabbit

  • Bug Fixes

    • Improved formatting of multiple Common Table Expressions (CTEs) in WITH clauses to correctly position separators—commas and parentheses—between individual CTE definitions when using left-aligned query formatting.
  • Tests

    • Added comprehensive test cases to validate multi-CTE formatting scenarios and ensure accurate separator placement and formatting consistency across supported SQL styles.

format_with_clause_left closed every CTE with a bare ")", which
produced invalid SQL whenever there were two or more CTEs in a
style that does not use compact CTEs (e.g. dbt). The compact-CTE
branch already inlined "), name AS (" correctly; the non-compact
branch is now position-aware and emits ")," for every CTE except
the last.

Adds a minimal multi-CTE fixture for the dbt style as a regression
test.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR fixes CTE (Common Table Expression) formatting in left-aligned WITH clauses. When multiple CTEs are present, closing delimiters are now conditionally emitted: non-final CTEs receive ), while the final CTE receives ). A test fixture validates the multi-CTE formatting behavior.

Changes

CTE Delimiter Formatting

Layer / File(s) Summary
Core Formatting Logic
src/formatter/select.rs
In format_with_clause_left, the CTE body closing delimiter is now conditional on position: emit ), for non-final CTEs and ) for the last CTE in non-compact mode.
Test Fixtures & Verification
tests/fixtures/dbt/select_cte_multi.sql, tests/fixtures/dbt/select_cte_multi.expected, tests/fixtures_test.rs
New multi-CTE fixture and test function dbt_select_cte_multi validate proper delimiter placement across CTE definitions.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 With clauses now align with grace,
Commas flutter in the right place,
Each CTE knows its role so clear—
Final ones stand without comma here!
A formatter's delight, precise and neat,
Multi-CTEs now play to the beat! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding comma emission between CTEs in non-compact WITH formatting, which directly matches the core fix in format_with_clause_left.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/formatter/select.rs (2)

1577-1585: ⚡ Quick win

Pre-existing dead code block can be simplified.

The if self.config.compact_ctes && i > 0 / else block at Lines 1577–1585 reduces to a single unconditional lines.push(cte_prefix) — both branches push cte_prefix and the inner if i > 0 && !self.config.compact_ctes guard wraps an empty body. Worth cleaning up while this function is under active change.

♻️ Suggested simplification
-                if self.config.compact_ctes && i > 0 {
-                    lines.push(cte_prefix);
-                } else {
-                    if i > 0 && !self.config.compact_ctes {
-                        // Close previous CTE.
-                        // Already handled below.
-                    }
-                    lines.push(cte_prefix);
-                }
+                lines.push(cte_prefix);
🤖 Prompt for 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.

In `@src/formatter/select.rs` around lines 1577 - 1585, The conditional branch
around self.config.compact_ctes and i is dead: both branches call
lines.push(cte_prefix) and the nested if contains no body; simplify by removing
the outer if/else and the empty inner guard and replace with a single
unconditional lines.push(cte_prefix) at that location (keep references to
self.config.compact_ctes and i only if still needed elsewhere); locate the code
using the symbols self.config.compact_ctes, i, and lines.push(cte_prefix) in
select.rs to make the change.

1595-1602: ⚡ Quick win

Consider adding multi-CTE fixtures for other !compact_ctes styles (Mozilla, GitLab).

The fix is config-flag driven and applies to every style where compact_ctes is false, but regression coverage currently only exists for dbt. Mozilla and GitLab share the same code path and could silently regress in future.

🤖 Prompt for 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.

In `@src/formatter/select.rs` around lines 1595 - 1602, Tests only cover the dbt
style for the !self.config.compact_ctes behavior; add equivalent multi-CTE
fixtures for the Mozilla and GitLab styles so the shared code path (the block
that pushes "),"/")" based on is_last using self.config.compact_ctes and the
ctes iteration) is exercised for those styles. Create fixtures mirroring the
existing multi-CTE dbt case but with the Mozilla and GitLab formatting configs,
and update the test harness to run the same assertions against the output
produced by the code that calls lines.push(..) for non-compact_ctes to prevent
regressions.
🤖 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.

Nitpick comments:
In `@src/formatter/select.rs`:
- Around line 1577-1585: The conditional branch around self.config.compact_ctes
and i is dead: both branches call lines.push(cte_prefix) and the nested if
contains no body; simplify by removing the outer if/else and the empty inner
guard and replace with a single unconditional lines.push(cte_prefix) at that
location (keep references to self.config.compact_ctes and i only if still needed
elsewhere); locate the code using the symbols self.config.compact_ctes, i, and
lines.push(cte_prefix) in select.rs to make the change.
- Around line 1595-1602: Tests only cover the dbt style for the
!self.config.compact_ctes behavior; add equivalent multi-CTE fixtures for the
Mozilla and GitLab styles so the shared code path (the block that pushes
"),"/")" based on is_last using self.config.compact_ctes and the ctes iteration)
is exercised for those styles. Create fixtures mirroring the existing multi-CTE
dbt case but with the Mozilla and GitLab formatting configs, and update the test
harness to run the same assertions against the output produced by the code that
calls lines.push(..) for non-compact_ctes to prevent regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65b4c3f0-2613-4fa6-9dd1-c414ab8bad07

📥 Commits

Reviewing files that changed from the base of the PR and between 7af73d3 and 5539dfa.

📒 Files selected for processing (4)
  • src/formatter/select.rs
  • tests/fixtures/dbt/select_cte_multi.expected
  • tests/fixtures/dbt/select_cte_multi.sql
  • tests/fixtures_test.rs

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