Skip to content

fix:Harden SQL safety handling and align architecture - #7

Merged
enumura1 merged 5 commits into
mainfrom
fix/release-smoke-issues
May 25, 2026
Merged

fix:Harden SQL safety handling and align architecture#7
enumura1 merged 5 commits into
mainfrom
fix/release-smoke-issues

Conversation

@enumura1

@enumura1 enumura1 commented May 24, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add py-sql-cleaner --version for installed CLI version checks.
  • Reduce Redshift false positives so keyword-like column names are not treated as Redshift-only SQL.
  • Keep f-string and Jinja-like SQL blocks skipped for both format and extract, even when --include-unsafe is provided.
  • Document why runtime-filled SQL strings are not rewritten or extracted.

Why

The release smoke test found three issues before wider sharing: no CLI version command, overly broad Redshift keyword detection, and unsafe formatting of f-string SQL when explicitly included. These changes keep the CLI easier to verify and avoid rewriting SQL-like strings that are not complete SQL at rest.

Validation

  • scripts/check
  • local release smoke script: 111/111 passed against the fixed CLI

Summary by CodeRabbit

  • New Features

    • Eager --version option prints the installed CLI version.
    • CLI now prints explicit skip reasons and richer block reports/warnings when embedded SQL is skipped.
  • Behavior Changes

    • format and extract always skip f-strings, Jinja-like templates, and runtime SQL placeholders.
    • More stable Redshift-specific formatting/preservation behavior.
  • Documentation

    • Safety, commands, and supported-input docs updated to match new skip rules and --version.
  • Tests

    • Added CLI and detector tests covering skip-reasons and placeholder detection.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fa11f50a-ad07-401f-8d4a-4f803ed33477

📥 Commits

Reviewing files that changed from the base of the PR and between b3156fc and 342edc6.

📒 Files selected for processing (1)
  • docs/architecture/README.md

📝 Walkthrough

Walkthrough

This PR centralizes unsafe-SQL detection (f-strings, Jinja, runtime placeholders), moves block detection/reporting into the application layer, updates Extract/Format flows to warn-and-skip unsafe blocks (with reason), improves Redshift-aware formatting, adds a CLI --version flag, and updates tests/docs/config to reflect the layered architecture.

Changes

Safety-first Block Handling & Layered Architecture

Layer / File(s) Summary
Domain safety model and placeholder detection
py_sql_cleaner/domain/detector.py, py_sql_cleaner/domain/models.py, py_sql_cleaner/domain/safety.py, tests/domain/test_detector.py
SQL keyword detection expanded; runtime placeholder regexes and detection logic added; SqlBlock gains has_placeholder field; new safety.py centralizes unsafe_reason() and is_unsafe() helpers.
Application block inspection and reporting
py_sql_cleaner/application/inspect_sql.py
New SqlBlockReport dataclass and inspect_sql_blocks() function convert detected blocks into annotated reports including unsafe classification.
Extract command application layer
py_sql_cleaner/application/extract_sql.py
plan_extract() now uses unsafe_reason() for skip decisions; ExtractPlan adds blocks field; formatting wrapped in try/except FormatterError with fallback to original SQL.
Format command application layer
py_sql_cleaner/application/format_source.py
format_source() refactored to accept source_file: Path, detect blocks internally, use unsafe_reason() and ALWAYS_SKIP_UNSAFE_REASONS for skipping; adds blocks field to result.
Redshift dialect detection and formatting
py_sql_cleaner/infrastructure/sqlglot_formatter.py
Redshift keyword detection replaced with contextual token analysis; preserve prefixes and table/column option sets introduced; dual-pass formatting wrapper added.
CLI app integration with version flag
py_sql_cleaner/cli/app.py
Eager --version flag prints package version; list, format, extract commands refactored to use new inspection/reporting APIs and result.blocks; imports consolidated to infrastructure formatter module.
CLI tests for unsafe block skipping
tests/cli/test_extract_command.py, tests/cli/test_format_command.py
Extract tests verify f-string, Jinja, placeholder SQL blocks are skipped with matching reason= output; format tests cover --include-unsafe skipping and version smoke test.
Domain tests for placeholder detection
tests/domain/test_detector.py
Unit tests for has_runtime_placeholder() covering colon, numeric, pyformat placeholders, and false-positive handling in literals/comments.
Infrastructure tests for Redshift formatter
tests/infrastructure/test_sqlglot_formatter.py
Formatter tests import from infrastructure path; coverage expanded for Redshift DISTKEY, date_part idempotence, and external table preservation.
User-facing documentation updates
README.md, website/docs/intro.md, website/docs/project/safety.md, website/docs/reference/commands.md, website/docs/reference/supported-input.md
Explicitly document that f-strings, Jinja, placeholders are always skipped; add --version usage; expand supported SQL keywords; add code examples.
Architecture documentation and import-linter config
AGENTS.md, docs/architecture/README.md, pyproject.toml, tests/README.md, py_sql_cleaner/adapters/__init__.py, py_sql_cleaner/core/__init__.py, py_sql_cleaner/infrastructure/__init__.py
Update layered architecture (cli → application → domain/infrastructure); enforce import constraints; reflect infrastructure layer for SQLGlot; remove obsolete adapters/core docstrings.

Sequence Diagram(s)

sequenceDiagram
  participant CLI_User
  participant CLI_App
  participant Application
  participant Domain
  participant Infrastructure
  CLI_User->>CLI_App: py-sql-cleaner format <file>
  CLI_App->>Application: format_source(source_file, source, ...)
  Application->>Domain: detect_sql_blocks(source)
  Domain-->>Application: list[SqlBlock]
  Application->>Domain: unsafe_reason(block)
  Domain-->>Application: reason or None
  alt reason present or ALWAYS_SKIP
    Application->>Application: append "Skipped unsafe SQL block ... reason=<reason>" warning
  else
    Application->>Infrastructure: formatter(sql, dialect, backend)
    Infrastructure-->>Application: formatted_sql
    Application->>Application: replace SQL in source
  end
  Application-->>CLI_App: FormatSourceResult(blocks, warnings, errors)
  CLI_App-->>CLI_User: display results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • enumura1/py-sql-cleaner#2: Overlaps with earlier changes to FormatSourceResult structure and CLI/dialect validation that relate to format_source and CLI result handling.

🐰 With placeholders left untouched and f-strings gently spared,
The cleaner hops and checks each block, so runtime code is cared.
Tokens guard Redshift's corners, docs and tests now sing,
A rabbit cheers this careful patch — safe SQL is the thing.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix release smoke issues' is vague and generic, using non-descriptive terminology that does not clearly convey the main changes or the specific issues being addressed. Consider a more specific title that highlights the primary change, such as 'Add --version CLI command and strengthen unsafe SQL skipping' or 'Add --version command and fix f-string/Jinja skipping in format/extract operations'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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
  • Commit unit tests in branch fix/release-smoke-issues

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0ca9c6d9e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +100 to +103
return previous_token not in {"", ",", "SELECT", "AS"} and next_token not in {
"",
",",
"FROM",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect Redshift column options before commas

The new _looks_like_table_option heuristic treats any keyword followed by , as non-Redshift (next_token not in {"", ",", "FROM"}), which misses valid Redshift column attributes like DISTKEY, SORTKEY, or ENCODE when they appear on a non-final column definition (e.g., col1 INT DISTKEY, col2 INT). This is a regression from the previous behavior and causes Redshift-specific SQL to slip past explicit dialect detection under the default dialect, leading to incorrect formatting/parsing paths instead of the expected Redshift-only handling.

Useful? React with 👍 / 👎.

@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.

🧹 Nitpick comments (1)
tests/cli/test_format_command.py (1)

12-12: ⚡ Quick win

Avoid hardcoding the version string in the test.

The test hardcodes "py-sql-cleaner 0.1.0", which will require manual updates on every version bump.

Consider using dynamic version retrieval or a pattern match instead.

♻️ Proposed fix using dynamic version
+import importlib.metadata
+
 from typer.testing import CliRunner

 from py_sql_cleaner.cli import app
 def test_version_option_prints_package_version() -> None:
     result = runner.invoke(app, ["--version"])

     assert result.exit_code == 0, result.output
-    assert "py-sql-cleaner 0.1.0" in result.output
+    expected_version = importlib.metadata.version("py-sql-cleaner")
+    assert f"py-sql-cleaner {expected_version}" in result.output

Alternatively, use a pattern if the exact format is less important:

 def test_version_option_prints_package_version() -> None:
     result = runner.invoke(app, ["--version"])

     assert result.exit_code == 0, result.output
-    assert "py-sql-cleaner 0.1.0" in result.output
+    assert "py-sql-cleaner" in result.output
+    assert result.output.strip().split()[-1][0].isdigit()  # ends with version number
🤖 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 `@tests/cli/test_format_command.py` at line 12, The test currently hardcodes
the version string ("py-sql-cleaner 0.1.0") which will break on version bumps;
update the assertion in tests/cli/test_format_command.py to either (a) import
the package version (e.g., from py_sql_cleaner import __version__ or the package
metadata) and assert f"py-sql-cleaner {__version__}" is in result.output, or (b)
use a regex/pattern match against result.output like "py-sql-cleaner
\\d+\\.\\d+\\.\\d+" so the test no longer depends on a literal version string.
🤖 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 `@tests/cli/test_format_command.py`:
- Line 12: The test currently hardcodes the version string ("py-sql-cleaner
0.1.0") which will break on version bumps; update the assertion in
tests/cli/test_format_command.py to either (a) import the package version (e.g.,
from py_sql_cleaner import __version__ or the package metadata) and assert
f"py-sql-cleaner {__version__}" is in result.output, or (b) use a regex/pattern
match against result.output like "py-sql-cleaner \\d+\\.\\d+\\.\\d+" so the test
no longer depends on a literal version string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3fa09c62-9107-44b4-be3d-01c9d8583f78

📥 Commits

Reviewing files that changed from the base of the PR and between 0ca9c6d and 73c79dd.

📒 Files selected for processing (14)
  • README.md
  • py_sql_cleaner/adapters/sqlglot_formatter.py
  • py_sql_cleaner/application/extract_sql.py
  • py_sql_cleaner/application/format_source.py
  • py_sql_cleaner/core/detector.py
  • py_sql_cleaner/domain/models.py
  • tests/adapters/test_sqlglot_formatter.py
  • tests/cli/test_extract_command.py
  • tests/cli/test_format_command.py
  • tests/core/test_detector.py
  • website/docs/intro.md
  • website/docs/project/safety.md
  • website/docs/reference/commands.md
  • website/docs/reference/supported-input.md
✅ Files skipped from review due to trivial changes (4)
  • website/docs/intro.md
  • README.md
  • website/docs/reference/commands.md
  • website/docs/reference/supported-input.md

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
py_sql_cleaner/cli/app.py (1)

108-113: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't report every detected block as unformatted.

result.blocks now contains all detected SQL blocks, including skipped unsafe blocks and blocks that were already formatted. In --check mode this can point users at unrelated locations whenever any one block changes.

Proposed minimal fix
     if not write:
         if changed:
-            console.print("Found unformatted embedded SQL:")
-            for block in result.blocks:
-                console.print(
-                    f"- {block.file_path}:{block.start_line}-{block.end_line} "
-                    f"variable={block.variable_name or '-'}"
-                )
+            console.print("Found unformatted embedded SQL.")
             raise typer.Exit(1)
         raise typer.Exit(0)
🤖 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 `@py_sql_cleaner/cli/app.py` around lines 108 - 113, Report only truly
unformatted, non-skipped blocks: when iterating result.blocks in the CLI (the
loop that prints "Found unformatted embedded SQL:"), filter out blocks that were
skipped or already formatted by checking block.skipped and block.was_formatted
(or the equivalent flags on the Block object) and only print blocks where
skipped is false and was_formatted is false; update the loop that references
result.blocks to use this filtered list so --check only points to real failures.
🤖 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.

Outside diff comments:
In `@py_sql_cleaner/cli/app.py`:
- Around line 108-113: Report only truly unformatted, non-skipped blocks: when
iterating result.blocks in the CLI (the loop that prints "Found unformatted
embedded SQL:"), filter out blocks that were skipped or already formatted by
checking block.skipped and block.was_formatted (or the equivalent flags on the
Block object) and only print blocks where skipped is false and was_formatted is
false; update the loop that references result.blocks to use this filtered list
so --check only points to real failures.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 249e17e9-eda4-4334-9896-46f50bd5f1da

📥 Commits

Reviewing files that changed from the base of the PR and between 73c79dd and ea42f3a.

📒 Files selected for processing (18)
  • AGENTS.md
  • docs/architecture/README.md
  • py_sql_cleaner/adapters/__init__.py
  • py_sql_cleaner/application/extract_sql.py
  • py_sql_cleaner/application/format_source.py
  • py_sql_cleaner/application/inspect_sql.py
  • py_sql_cleaner/cli/app.py
  • py_sql_cleaner/core/__init__.py
  • py_sql_cleaner/domain/detector.py
  • py_sql_cleaner/domain/extractor.py
  • py_sql_cleaner/domain/rewriter.py
  • py_sql_cleaner/domain/safety.py
  • py_sql_cleaner/infrastructure/__init__.py
  • py_sql_cleaner/infrastructure/sqlglot_formatter.py
  • pyproject.toml
  • tests/README.md
  • tests/domain/test_detector.py
  • tests/infrastructure/test_sqlglot_formatter.py
💤 Files with no reviewable changes (4)
  • py_sql_cleaner/adapters/init.py
  • py_sql_cleaner/domain/detector.py
  • py_sql_cleaner/core/init.py
  • py_sql_cleaner/infrastructure/sqlglot_formatter.py
✅ Files skipped from review due to trivial changes (2)
  • py_sql_cleaner/infrastructure/init.py
  • docs/architecture/README.md

@enumura1 enumura1 changed the title Fix release smoke issues fix:Harden SQL safety handling and align architecture May 25, 2026
@enumura1
enumura1 merged commit 1f2f9f6 into main May 25, 2026
1 of 2 checks passed
@enumura1
enumura1 deleted the fix/release-smoke-issues branch May 25, 2026 19:12
@coderabbitai coderabbitai Bot mentioned this pull request May 27, 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