You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(migrations): support specifying schema for migrations (#471)
## Summary
Add `MigrationConfig.default_schema` and `version_table_schema` so a
migration run can target a specific schema/dataset, plus the per-adapter
driver hooks that apply, validate, and log that selection. Includes
related cleanup found during review: shared identifier-quoting helpers,
Oracle literal-case contract, and a comprehensive docs matrix.
## Feature: migration default schema
- `MigrationConfig.default_schema` — sets the session schema for the
migration run (per-dialect: `SET search_path` / `ALTER SESSION SET
CURRENT_SCHEMA` / `SET schema` / default_dataset).
- `MigrationConfig.version_table_schema` — controls where the tracker
lives; defaults to `default_schema` when present.
- Fail-fast validation via `driver.has_schema()` before any DDL runs;
misconfigured runs raise `MigrationError` with a clear message.
- Structured logging: every transition emits a `migration.schema.*`
event (`applied`, `noop`, `reset`) through `log_with_context`, with the
resolved driver class in the payload.
### Adapter support matrix
| Adapter | `supports_migration_schemas` | Hook |
| --- | --- | --- |
| asyncpg, psycopg, psqlpy | yes | `SET search_path TO "<schema>"` |
| cockroach_asyncpg, cockroach_psycopg | yes | inherits PG behavior |
| adbc (postgres dialect) | yes (derived from dialect) | `SET
search_path TO "<schema>"` |
| adbc (sqlite/duckdb/snowflake/...) | no | structured noop |
| oracledb | yes | `ALTER SESSION SET CURRENT_SCHEMA = "<schema>"`
(literal case) |
| duckdb | yes | `SET search_path = "<schema>"` (identifier form) |
| bigquery | yes | `default_dataset` on the client |
| spanner | yes | per-session default schema |
| sqlite, aiosqlite, asyncmy, aiomysql, mysqlconnector, pymysql,
mssql_python, arrow_odbc | no | structured noop |
See `docs/usage/migrations.rst` for the per-adapter contract
(literal-case requirements, opt-in flags, expected SQL surface).
## Refactor: identifier-quoting helpers
Consolidated seven adapter-local duplicates into
`sqlspec/utils/text.py`:
- `quote_identifier(value)` — ANSI form `"..."` with `""`-escaping. Used
by sqlite, aiosqlite, postgres family (asyncpg, psycopg, psqlpy, adbc),
duckdb, oracle.
- `quote_backtick_identifier(value)` — MySQL family form `` `...` ``
with `` `` ``-escaping. Used by asyncmy, aiomysql, mysqlconnector,
pymysql.
Removed duplicates: `_quote_sqlite_identifier`, `_quote_identifier`
(psqlpy), `_quote_mysql_identifier` (×4), `_quote_duckdb_search_path`.
Both helpers added to the mypyc include list.
## Bug fix: Oracle case-folding
Oracle migration hooks previously called `.upper()` and bound
`UPPER(:schema_name)`, which broke mixed-case Oracle users (`CREATE USER
"MixedCase"` was unreachable). Now matches the PG/DuckDB contract: the
schema value is interpolated literally and bound verbatim. Callers pass
the stored identifier (typically uppercase for unquoted creates, exact
case for quoted creates) — documented in `migrations.rst`.
## Other adjustments
- BigQuery: shared `SyncMigrationTracker` instead of an adapter-specific
tracker; SQLGlot DDL rendering patched so tracker SQL emits `PRIMARY KEY
NOT ENFORCED` and orders `DEFAULT` before `NOT NULL`. Integration tests
for the full migration workflow are gated behind native BigQuery
(emulator rejects column `DEFAULT` values in tracker DDL).
- ADBC: `supports_migration_schemas` is now a `@property` deriving from
dialect, so a single config class lights up only when targeting a
supported dialect.
- Tracker: handles dotted-form `version_table_name` correctly (strips
embedded `schema.table` prefixes so the qualified version_table cannot
be double-prefixed).
- Removed dead `_get_existing_columns_sql` from `oracledb/migrations.py`
(replaced by `data_dictionary.get_columns(..., schema=...)` earlier in
this PR).
## Docs
- `docs/usage/migrations.rst` — comprehensive adapter matrix,
per-dialect contract, literal-case guidance.
- `docs/examples/patterns/migrations_with_schema.py` — runnable example.
## Tests
- Unit: per-adapter migration-schema tests (postgres family, oracle,
duckdb, adbc), noop assertions for unsupported adapters, tracker
idempotency, command/runner validation, `quote_identifier` escape
behavior.
- Integration: full migration workflow against asyncpg, psycopg, psqlpy,
oracledb, duckdb, adbc; data-dictionary coverage tests for
psycopg/psqlpy/asyncpg; Spanner session-defaults test.
## Follow-up
Identifier handling for non-migration sites (data-dictionary metadata
queries, per-dialect case-folding normalizers) is intentionally out of
scope here and tracked separately in #477, #478, #479.
0 commit comments