Skip to content

Conversation

@cofin
Copy link
Member

@cofin cofin commented Oct 17, 2025

Description

Fixes the critical data safety bug where the --dry-run flag was accepted but completely ignored, causing migrations to actually modify the database when users expected no changes to occur.

Closes #133

Problem

The --dry-run flag was defined in the CLI commands but never passed through to the migration execution functions. This meant that:

  • Users running sqlspec upgrade --dry-run would have their database actually modified
  • Users running sqlspec downgrade --dry-run would have migrations actually reverted
  • Version table would be updated despite dry-run flag
  • No indication that dry-run was not working

This is a critical data safety issue.

Solution

Changes Made

  1. Migration Commands (sqlspec/migrations/commands.py)

    • Added dry_run: bool = False parameter to upgrade() and downgrade() methods
    • Added prominent "DRY RUN MODE" banner at start of execution
    • Skip database operations when dry_run=True:
      • Skip runner.execute_upgrade() / runner.execute_downgrade()
      • Skip tracker.record_migration() / tracker.remove_migration()
    • Changed output verbs to "Would apply/revert" instead of "Applying/Reverting"
    • Display migration file paths during dry-run
    • Add final confirmation message when dry-run completes
    • Applied to both SyncMigrationCommands and AsyncMigrationCommands
  2. CLI Commands (sqlspec/cli.py)

    • Updated 4 call sites to pass dry_run=dry_run parameter:
      • downgrade_database single-config path
      • downgrade_database multi-config path
      • upgrade_database single-config path
      • upgrade_database multi-config path

Example Output

Before (BUG):
```
$ sqlspec downgrade --dry-run
Are you sure you want to downgrade the database to the -1 revision? [y/n]: y
Reverting 1 migrations

Reverting 0002: oauth
✓ Reverted in 12ms

$ sqlspec show-current-revision
Current version: 0001 # DATABASE WAS MODIFIED!
```

After (FIXED):
```
$ sqlspec downgrade --dry-run
DRY RUN MODE: No database changes will be applied

Are you sure you want to downgrade the database to the -1 revision? [y/n]: y
Reverting 1 migrations

Would revert 0002: oauth
Migration file: /path/to/migrations/0002_oauth.sql

Dry run complete. No changes were made to the database.

$ sqlspec show-current-revision
Current version: 0002 # DATABASE UNCHANGED
```

Checklist

  • Code follows AGENTS.md guidelines
  • Type hints added correctly
  • Linting passed
  • Type checking passed
  • Both sync and async implementations updated
  • All 4 CLI call sites updated

The --dry-run flag was being accepted but ignored, causing migrations
to actually modify the database when users expected no changes.

Changes:
- Add dry_run parameter to upgrade() and downgrade() methods in both
  SyncMigrationCommands and AsyncMigrationCommands
- Skip database operations (execute_upgrade/downgrade and version table
  updates) when dry_run=True
- Add prominent "DRY RUN MODE" banner to console output
- Show "Would apply/revert" instead of "Applying/Reverting" in dry-run
- Display migration file paths during dry-run
- Add final confirmation message when dry-run completes
- Update CLI to pass dry_run flag to migration commands

Testing:
- Linting: ruff check passed
- Type checking: pyright 0 errors

Fixes #133
@cofin cofin force-pushed the fix/issue-133-dry-run-bypass branch from 38b3679 to 90706c4 Compare October 17, 2025 20:05
@cofin cofin merged commit bed7303 into main Oct 17, 2025
10 of 11 checks passed
@cofin cofin deleted the fix/issue-133-dry-run-bypass branch October 17, 2025 20:59
cofin added a commit that referenced this pull request Oct 20, 2025
Adds automatic CLOB-to-string conversion in Oracle adapter to enable seamless
msgspec/Pydantic integration. CLOB handles are now read automatically before
schema conversion, eliminating the need for DBMS_LOB.SUBSTR workarounds.

## Summary

Oracle's oracledb driver returns CLOB columns as LOB/AsyncLOB objects with
.read() methods. This causes msgspec validation to fail with "Expected 'str',
got 'AsyncLOB'" errors. Users previously worked around this by wrapping CLOB
columns in DBMS_LOB.SUBSTR, truncating to 4000 chars and duplicating boilerplate.

## The Solution

Automatically coerce LOB handles to concrete values during result processing:

- Added _coerce_sync_row_values() and _coerce_async_row_values() helpers
- Integrated into both sync and async _execute_statement() methods
- Reads CLOB handles before dict construction
- Applies JSON detection for JSON-in-CLOB scenarios
- BLOB handling unchanged (remains bytes)

## Key Features

- Automatic CLOB hydration in sync driver (line 547)
- Automatic CLOB hydration in async driver (lines 740-743)
- JSON detection via OracleTypeConverter.convert_if_detected()
- Error handling for LOB read failures
- Zero configuration required

## Testing

Created comprehensive msgspec integration test suite:

- tests/integration/test_adapters/test_oracledb/test_msgspec_clob.py (528 lines)
- 12 tests covering: basic hydration, mixed types, JSON detection, BLOB, multi-CLOB, NULL
- All 158 Oracle tests passing (146 existing + 12 new)

## Documentation

- Updated docs/guides/adapters/oracle.md with CLOB/BLOB handling section
- Added LOB Hydration Pattern to AGENTS.md for future reference
- Includes before/after examples showing eliminated workarounds

Closes #135
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.

Bug: dry-run seems to be bypassed

2 participants