Skip to content

feat: add rowx check --scratch - #163

Merged
mplemay merged 2 commits into
mainfrom
feat/148-check-scratch
Nov 1, 2026
Merged

mplemay merged 2 commits into
mainfrom
feat/148-check-scratch

Conversation

@mplemay

@mplemay mplemay commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #148

What

rowx check --scratch builds a throwaway database from the migrations, checks against it, and drops it.

  • The configured URL is a server URL. The checker creates rowx_check_<16 hex> with TEMPLATE template0, runs Migrator.run on a normal writable connection (closed before the checker session opens), then runs the usual read-only check session on that database.
  • The drift gate is skipped under --scratch.
  • --scratch without [tool.rowx.migrate] exits 2. The migration files are read before any database is created, so a broken directory fails first.
  • The scratch database is dropped WITH (FORCE) on every exit path: success, diagnostics, migration failure, describe failure, internal error, KeyboardInterrupt and real SIGINT. The drop runs in the async context manager's finally. If it was interrupted (a second Ctrl-C), Scratch.sweep() retries it once from outside the event loop. A drop that still fails prints a warning: line to stderr and never masks the original error.
  • A missing CREATEDB privilege (SQLSTATE 42501) exits 2 with "--scratch needs a role with CREATEDB". A failing migration exits 2 and names the version and the server error.
  • The scratch URL is built by rowx.postgres._admin.with_database(dsn, name), shared so rowx.testing (feat: add rowx.testing pytest plugin #149) can reuse it. The driver percent-decodes query keys and applies them after the path, last one winning, so every spelling of dbname (including %64bname) is dropped, the new name is appended last with urlencode(..., quote_via=quote), and the result is parsed back with ConnectOptions.from_dsn and rejected unless it names the scratch database. Before this, ?%64bname=<db> made --scratch run the migrations on <db>.
  • No DSN or password is printed: every message is redacted with the server URL. A URL that can't be pointed at the scratch database fails with "the database URL could not be parsed" before anything is created, and a connection failure while migrating is a redacted exit 2 rather than an internal error.
  • The migrator honours [tool.rowx.migrate] (dir, table, create_schemas) through load_migrator, as rowx migrate run does.
  • Migration progress lines are not printed, so JSON stdout stays clean.
  • --statistics adds scratch database built in: N.NNs (create plus migrate).
  • The safety statement is amended in --help for --scratch and in docs/guide.md.

New tests

Integration (src/rowx/__tests__/integration/check/test_check_cli.py, db). The no_leftovers fixture compares pg_database before and after each test and fails on any leaked rowx_check_*:

  • test_scratch_clean_run_when_the_target_lacks_the_tables (the drift gate rejects the same database)
  • test_scratch_reports_diagnostics_against_the_scratch_schema
  • test_scratch_never_touches_the_target_database
  • test_scratch_failing_migration_exits_two
  • test_scratch_without_createdb_exits_two_with_help
  • test_scratch_without_migrate_config_is_a_usage_error
  • test_scratch_unreachable_server_never_prints_the_password
  • test_scratch_statistics_report_the_build_time
  • test_scratch_is_not_built_when_nothing_is_described
  • test_scratch_is_dropped_when_the_check_is_interrupted_or_fails, parametrized over KeyboardInterrupt, a real SIGINT through asyncio.run, an internal error and a describe failure
  • test_scratch_check_session_is_read_only
  • test_scratch_honours_the_migrate_table_and_create_schemas (history in rowx_meta.history, tables in a create_schemas schema)
  • test_scratch_never_migrates_a_database_named_in_the_query_string (?%64bname=<target>; fails on the first version of this PR)

Unit (src/rowx/__tests__/unit/check/test_scratch.py):

  • test_scratch_names_fit_the_identifier_limit
  • test_scratch_url_that_cannot_be_parsed_never_quotes_the_dsn

Unit (src/rowx/__tests__/unit/postgres/test__admin.py):

  • test_with_database (URL, key=value, multi-host, dbname query parameter)
  • test_with_database_overrides_every_dbname (%64bname, fully encoded keys, repeated keys, no path, key=value)
  • test_with_database_keeps_everything_else
  • test_with_database_quotes_the_name

Wiring edits

  • New src/rowx/check/scratch.py.
  • src/rowx/postgres/_admin.py: adds with_database (private module, not exported).
  • src/rowx/check/runner.py: run_check takes a Hooks(gate, scratch) dataclass instead of the gate keyword (clippy-style too-many-arguments). Statistics.scratch_seconds and ScratchBuild are added.
  • src/rowx/check/cli.py: --scratch flag, _check(args), and the statistics line.
  • docs/guide.md: usage line and the amended safety statement. docs/design is untouched.

Spec ambiguities

  • Nothing to describe: the scratch database is only built when there is at least one query to describe, the same rule as the drift gate. So --scratch on a tree without queries doesn't validate the migrations (and needs no URL). Kept deliberately: a run with nothing to describe never needs a database, and rowx database setup / rowx migrate run validate migrations. Building it anyway is a one-line change if the design prefers.
  • Drop failure: the design says "every exit path" but not what a failed drop means. It is a stderr warning, not an exit-code change, so a leaked database doesn't turn a clean run into a failure or hide the real error.
  • Server URL: if the URL already names a database, it is replaced by the scratch name. create_database connects to the maintenance database (postgres, or template1) as rowx database does.
  • Migration options: --scratch uses the configured dir, table and create_schemas, and the migrate run defaults for the flags (no out-of-order and no ignore-missing), since the database is empty.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-01T02:43:28.135469Z c8410ce PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: c8410ce644

ℹ️ 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 thread src/rowx/check/scratch.py Resolved
…pelling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mplemay
mplemay merged commit aed6c5a into main Nov 1, 2026
3 checks passed
@mplemay
mplemay deleted the feat/148-check-scratch branch November 1, 2026 02:56
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.

feat: add rowx check --scratch

1 participant