Isolated SQL Server partition SWITCH component¶
Purpose: integrate and test the internal, unregistered partition replacement component. Audience: platform engineers, connector maintainers and certification fixture authors. Start with the native transport guide and the approved delivery specification.
The component is available for synthetic review and approved disposable fixtures.
The public native SWITCH blocker remains
mssql.strategy.partition_replace.native_switch, before I/O. There is no new
CLI flag, manifest field, registry entry or automatic fallback. This component
has no live correctness or performance certification yet.
Supported catalog profile¶
The v1 adapter accepts SQL Server 2022, major 16, EngineEdition 2 or 3,
with database-level VIEW DEFINITION, server VIEW ANY DEFINITION, and
SELECT ON OBJECT::sys.sql_expression_dependencies in the target database.
The catalog query needs this explicit SELECT permission to inspect dependencies;
see Microsoft's dependency-view permissions.
The fixture owner provisions these permissions; the component does not grant them
or require db_owner membership. Other editions/majors and hidden or incomplete
metadata fail closed. This explicit implementation profile is narrower than the
full set of SWITCH operations supported by SQL Server.
All three existing tables must be in the same database and use the same partition
function and scheme. The authored interval must equal two adjacent finite
boundaries of one RANGE RIGHT partition. The partition key is non-nullable
date or datetime2 with scale 0–6. A datetime2 interval uses aware UTC Python
datetimes; a date interval uses Python dates. The adapter preserves the SQL
boundary precision; execution binds proven UTC datetime2 values without timezone
conversion. “Temporal key” means a date/time key, not a system-versioned table.
For boundaries 2026-01-01, 2026-02-01, the interval
[2026-01-01, 2026-02-01) selects partition 2. An empty prepared table still
replaces that partition with zero rows. Partial intervals, multiple partitions,
unbounded first/last partitions and RANGE LEFT are ineligible.
The physical profile accepts aligned heaps and rowstore indexes, with identical ordered column/type/length/precision/scale/nullability/collation metadata, index keys/includes/directions/options, partitioning and per-index storage placement. Corresponding partition compression is NONE, ROW or PAGE. Object/index names and allocation IDs may differ; full object fingerprints retain them for drift checks. All partition layouts must match, including partitions outside the selected one.
The adapter excludes MAX/LOB storage, user/assembly types, computed, identity, encrypted, masked, sparse, hidden and generated columns; columnstore, XML/spatial, filtered, disabled or hypothetical indexes; constraints and table child objects; foreign-key and schema-bound dependencies, security predicates and change tracking, fulltext indexes and bound rules/defaults. Enabled database or server DDL triggers are excluded because they can mutate data during ALTER TABLE. Temporal/history, CDC/replication, graph, ledger, external, memory optimized, FILESTREAM and remote archive tables are also excluded. This is conservative admission, not a claim that SQL Server cannot switch every excluded layout. Unknown flags and missing partition or storage records are not defaults.
Prepare owned objects and transaction authority¶
The fixture owner provisions the target plus two invocation-owned aligned tables: prepared input and initially empty switch-out. No provisioning or DDL activation is provided by this component. The complete prepared table must contain only rows in the authored interval; NULL, below-bound and upper-bound rows reject the plan. The complete switch-out table must be empty, not only its selected partition.
Bind the database ID, name and catalog generation; exact schema/table names,
object IDs and creation timestamps; invocation ID, positive owner generation and
target mutation ID in NativeSwitchBinding. Provisioning writes
binding.tag("prepared") and binding.tag("switch_out") as the corresponding
table extended property dpone.native_switch.owner.v1. An arbitrary existing
caller table is not a disposable resource. A naming convention, SQL principal or
copied ownership marker cannot replace the caller's durable resource ownership
record and current fence validation.
NativeSwitchSql.query(sql, parameters) returns named rows on the same session,
using driver-bound ? parameters. A transaction implementation also supplies:
assert_authority(binding): raise unless the current invocation/generation, exact mutation and target fence are valid. Resolve the exact target receipt first; reject already committed or unresolved operations.verify_prepared(plan): run the existing complete typed business/framework integrity verification against durable prepared authority after executor locks are held. Raise on mismatch or unavailable evidence; never mutate or settle the transaction in this hook.execute(sql): execute once on that same session; propagate errors.
The caller opens one committable transaction, enables XACT_ABORT and
SERIALIZABLE, and retains its existing target/operation fence through completion.
The approved environment must freeze privileged database/server DDL-trigger
configuration through completion; table locks do not guard global administrative
changes. Nested transactions are excluded. SQL connections must not reconnect, retry or
commit implicitly. The executor acquires TABLOCKX, HOLDLOCK on all three tables
in object-ID order, then rereads catalog and content. These locks intentionally
serialize whole tables, including unrelated partitions, and protect revalidation
against independent SQL writers and DDL until the caller settles the transaction.
Prepared content integrity remains the caller's responsibility. Supply the
mandatory verify_prepared(plan) hook delegating to the existing typed/full
verification service. The executor invokes it after locking all three tables.
Bind the proof to this invocation, generation, mutation and interval, accounting
for the caller's authoritative transaction-clock metadata projection. Counts and catalog fingerprints
cannot detect an in-window value change that preserves row count. A snapshot or
ownership string is neither a prepared-content receipt nor transaction authority.
Call the feature-local interfaces¶
These signatures are frozen handoff points:
snapshot = catalog.snapshot(binding, interval=interval)
eligibility = plan_native_switch(snapshot, interval=interval, owner_binding=binding)
if eligibility.plan is None:
raise NativeSwitchRejected(*eligibility.reasons)
result = execute_native_switch(eligibility.plan, transaction=transaction)
NativeSwitchCatalog.transaction_state() reads one exact transaction-state row
from the injected session. Construction performs no I/O; each observation is a
fresh query without caching or retry. The catalog checks row shape, while the
executor retains authority predicates, session comparisons and the order of
locks, integrity verification and SWITCH statements. TRANSACTION_SQL remains
in its existing catalog_sql module.
catalog is NativeSwitchCatalog constructed with the injected SQL session;
binding, interval and transaction come from the approved fixture/caller
lifecycle described above. This fragment describes integration, not a standalone
production invocation. The complete executable synthetic example is
test_two_switches_preserve_outside_rows_and_count_replaced_rows in
tests/test_mssql_native_partition_switch_recovery.py.
The executor revalidates the snapshot and plan after locking, counts matching
target rows with COUNT_BIG, performs target-to-switch-out followed by
prepared-to-target, and returns NativeSwitchResult(replaced_rows, inserted_rows).
The result is uncommitted. The executor emits no target receipt, file, stdout,
stderr, state update or cleanup request. The caller inserts its existing exact
receipt in the same transaction and commits.
sequenceDiagram
participant C as Existing caller/finalizer
participant E as Isolated executor
participant S as Same SQL transaction
C->>C: Resolve receipt, fence, transaction-clock projection
C->>E: execute_native_switch(frozen plan, transaction)
E->>S: Assert active transaction; lock three tables
E->>S: Reread catalog, ownership and exact content counts
E->>E: Replan and reject drift
E->>C: Verify complete prepared integrity under held locks
E->>S: Count replaced rows; SWITCH old out
E->>S: SWITCH prepared in
E-->>C: Uncommitted row counts
C->>S: Insert exact receipt; commit
Diagnose stable reasons¶
Pure planning returns sorted reasons and no plan on rejection. The catalog and
executor raise NativeSwitchRejected with the same .reasons tuple. Driver/SQL
errors propagate unchanged; do not parse server messages as eligibility proof.
| Reason | Meaning and next action |
|---|---|
metadata_unknown |
Missing, malformed or ambiguous catalog/count result. Restore visibility or supported metadata; do not assume empty values. |
unsupported_server |
Server major/edition outside v1. Obtain a separately reviewed adapter profile. |
metadata_visibility_required |
Database/server metadata cannot be fully observed. Have the fixture owner supply approved visibility. |
database_binding_mismatch |
Current database differs from the bound identity. Correct the connection and provisioning record. |
object_binding_mismatch |
Object identity changed or roles alias one object. Re-establish owned objects after settling any prior outcome. |
owner_binding_mismatch |
Invocation, generation, mutation or resource marker differs. Resolve ownership; never relabel foreign resources. |
interval_not_one_finite_partition |
Authored bounds/column do not select exactly one finite partition. Use the existing supported replacement path or redesign the fixture. |
unsupported_partition_layout |
Partition type, direction, precision, boundary or index alignment is outside v1. Supply an approved aligned fixture. |
layout_mismatch |
Complete physical shapes differ. Compare columns, indexes, function/scheme and storage before retrying. |
unsupported_column |
Column feature/type falls outside the adapter profile. Preserve the public fallback route. |
unsupported_index |
Index feature or metadata falls outside the profile. Do not drop production indexes for this experiment. |
unsupported_storage |
Unsupported or incomplete filegroup/compression metadata. Correct disposable provisioning. |
unsupported_table_feature |
Table behavior such as CDC/temporal/replication is excluded. Use an isolated eligible fixture. |
unsupported_dependency |
Constraint, child object or external dependency is excluded. Review the fixture definition. |
switch_out_not_empty |
Switch-out contains rows. Resolve prior transaction outcome and retention before changing it. |
prepared_rows_outside_interval |
Prepared table contains NULL or out-of-window rows. Reject the prepared payload. |
transaction_authority_invalid |
Missing/changed active, committable, serializable caller transaction. Restore caller authority; never start one in the executor. |
catalog_drift |
Protected revalidation differs from the frozen plan, including prepared count or a forged partition number. Stop before mutation and diagnose the change. |
Failure, retention and recovery¶
A failure before the first SWITCH causes no component target mutation. A failure from either SWITCH propagates for whole-transaction caller rollback. This includes a first statement that mutated before its response failed. The executor never retries, falls back to DELETE/INSERT, rolls back partially or cleans tables. The caller must not catch a second-SWITCH error and commit the first transfer.
After successful SWITCH the prepared partition is empty. Lost commit acknowledgement therefore requires an exact fresh receipt probe before reading prepared content. A confirmed matching receipt suppresses another mutation and allows existing evidence/state completion. A missing, unavailable or mismatching receipt leaves outcome unknown; retain all resources and block replay. Even when the target was initially empty and switch-out stays empty, receipt-first recovery is mandatory: emptiness is not proof that publication has not happened.
The caller owns prepared/switch-out retention. Do not drop, truncate, reuse or restamp them until transaction outcome and the existing evidence/checkpoint requirements are settled. After confirmed commit, old rows remain in switch-out until the fixture owner's retention policy permits cleanup. After rollback, restore the original invocation's prepared authority or fail closed. Unknown outcomes require investigation, not a cleanup timer.
Validate and hand off¶
Run the hermetic contract matrix from the repository root:
uv run pytest tests/test_mssql_native_partition_switch.py \
tests/test_mssql_native_partition_switch_catalog.py \
tests/test_mssql_native_partition_switch_recovery.py \
tests/test_runtime_partition_replace_native_contracts.py -q
Expected result: all selected tests pass. Tests exercise synthetic catalog/row transactions and inject SWITCH into the existing finalizer handler seam. The real resume service proves receipt-first behavior with an emptied prepared fixture. These are component/control-flow tests; they do not establish live SQL rollback, locking, identity authority, or a working production transaction bridge.
DDA-05 owns the approved disposable real-row fixture. Requirements and the ADR
handoff are retained under test_artifacts/delivery-acceleration/dda-04/.
DDA-06 owns navigation and the numbered ADR. Future public activation requires a
separate contract for aligned-stage provisioning, durable ownership/retention,
transaction bridging, public admission and exact-environment live proof.
Existing imports, manifests, receipts, journals and fallback behavior are
unchanged; no migration is needed. Return to the
delivery task plan for integration owners.
The underlying restrictions are documented in Microsoft ALTER TABLE SWITCH, catalog metadata visibility and DDL triggers. These rolling references were consulted on 2026-09-10; the supported major-16 profile above is an implementation decision, not live certification.