Skip to content

Commit ecea80e

Browse files
localstack-spiral[bot]spiralhovaesco
authored
LAV-3804: Support ALTER SCHEMA SWAP WITH parity (#4246)
* LAV-3804: implement atomic ALTER SCHEMA SWAP WITH parity Parse schema SWAP explicitly and exchange the existing namespaces and catalog identities through the shared schema movement machinery, without copying or deleting schemas. Preserve grants, schema properties, descendants, and session name slots. Serialize with the existing container graph lock, sorted relation locks (using separate name parts for quoted identifiers), then the RBAC lock. Cloud-captured tests establish current-database target resolution, same-target no-ops, qualified view name rebinding, missing-object/privilege precedence, cross-database exchange, and exact error positions. Reuse the existing transform and stale-view repair paths to retain view declarations and report missing references or changed query shapes. No existing snapshots or tests changed. Verification: - make check: passed. - New schema swap compat file: 30 passed. - Existing schemas, cross-database rename, UNDROP schema, sequence clone, and stale-view files: 93 passed with unchanged snapshots. - Existing schema clone selection: 14 passed with unchanged snapshots. - Existing table swap plus schema swap selection: 27 passed (before the four additional missing-database cases, all of which passed in the final replay). - make test-vendored-sqlparser: passed, including 44 doc tests. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations The Cloud grant observations exposed an existing unqualified SHOW GRANTS ON SCHEMA database-resolution defect. Corrected its producer in show_rewriter.rs so the swap's grant assertions observe the actual schema edges. Qualified views must resolve the exchanged name slots, not retain PostgreSQL OID bindings. Added stored-query planning through the existing transform and extended existing stale-view repair to preserve declared column names/counts; the new Cloud snapshots uniquely establish the required errors and recovery. ## Test matrix Populated schemas / tables, sequences, metadata, schema and table grants / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_populated Same target / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-same] Same target / IF EXISTS success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-same] Missing source / error and preserved contents -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_source] Missing source / IF EXISTS no-op and preserved contents -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_source] Missing target / error and preserved contents -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_target] Missing target / IF EXISTS error and preserved contents -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_target] Both missing / source error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-both_missing] Both missing / IF EXISTS target error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-both_missing] Qualified mixed-case quoted schemas / same-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[False] Qualified mixed-case quoted schemas / cross-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[True] Unqualified, qualified, transitive and outside views / name resolution -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_view_names Target ownership missing / error and preserved contents -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[False] Ownership without CREATE SCHEMA / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[True] Qualified source with unqualified target / current-database resolution -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_target_database_resolution Transient schema plus future grants / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[transient] Managed-access schema plus future grants / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[managed] Two sessions / same-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[False] Two sessions / cross-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[True] Quoted dotted names containing SWAP WITH and comments / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_dots Over-depth source / exact positioned error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[True] Over-depth target / exact positioned error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[False] Missing qualified view base / exact error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[missing] Changed qualified view shape / exact error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[different_shape] Quoted dotted view/table references, single-quoted and dollar-quoted literals / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_view_references IF EXISTS missing source, inaccessible target and non-owned same target / privilege precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_if_exists_target_privileges Missing source database / exact error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-False] Missing target database / exact error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-True] Missing source database / IF EXISTS exact error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-False] Missing target database / IF EXISTS exact error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-True] Defensive stored-definition parse/transform expectations and non-query guards protect catalog invariants; malformed internal catalog entries are not a SQL surface or a reason to remove those guards. The generated temporary-name collision loop protects occupied catalog/physical namespace slots. * LAV-3804: repair schema swap stale views, cycles, and session identity Replan contained and affected views from their stored Snowflake definitions on every swap, including already-stale views, and refresh the named reader at the current destination. Resolve physical view names and catalog updates by their owning session, and carry that session's post-swap temporary bindings into the existing AST transform. Preserve declared column names and logical diagnostics. Detect swap-created cyclic components without rejecting the swap. Bound repair's stale dependency depth traversal with PostgreSQL CYCLE, leave cyclic components tombstoned, and expose Cloud's verbatim 002037 view-name error and wire shape. Propagate dependency errors only into unresolved swap readers; preserve the existing nested expansion diagnostics produced by DROP/REPLACE machinery. Swept schema-swap planning, pre-mutation lock selection, named readers, stale closure, dependency expansion, catalog replay updates, and view re-owning for stale-location and logical-name-only identity defects; fixed temporary/permanent and cross-session collisions throughout these paths. Swept self and mutual cycles, permanent/temporary external dependents, and swap-back recovery; bounded stale depth traversal and preserved each cycle member's own diagnostic. AST lowering remains lexical-safe for quoted identifiers, strings, dollar strings, and comments. Schema rename's existing stale-view filter remains unchanged. Added 28 serially Cloud-captured compat cases. No existing tests or snapshots changed. Initial replay reproduced all six stale-location failures, including missing-reference and shape-error recovery. All added cases now replay green. Verification: - make check: passed (formatting, clippy, warning checks and vendored parser tests). - New schema-swap view file: 28 passed against emulator; all snapshots from Cloud. - Schema swap, schemas/rename, cross-database rename, UNDROP, stale views, table swap, temporary-view session scope and view row-access-policy files: 193 passed. - Schema clone selection across test_clones.py and test_sequence_clone.py: 15 passed with unchanged snapshots. - ViewBindings JSON decoding expect is an internal producer-invariant guard: SQL always builds the session_id/relations object, so malformed bindings are not reachable from the Snowflake surface. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Deviations Session-scoped repair also required updating existing stale-view closure/replay and RBAC re-owning helpers to use physical relation identities; swap-local fixes alone would still conflate temporary/permanent rows or re-own the caller's namesake. The new Cloud-qualified temporary-view invisibility errors include the role/privilege suffix. Extended the existing unavailable-relation error producer, restricted to AST-qualified references, to match these errors while retaining unqualified temporary-view snapshots. ## Test matrix Populated schemas / tables, sequences, metadata and grants / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_populated Same target / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-same] Same target / IF EXISTS success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-same] Missing source / error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_source] Missing source / IF EXISTS no-op and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_source] Missing target / error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_target] Missing target / IF EXISTS error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_target] Both missing / source error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-both_missing] Both missing / IF EXISTS target error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-both_missing] Quoted qualified schemas / same-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[False] Quoted qualified schemas / cross-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[True] Qualified, unqualified, transitive and outside views / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_view_names Missing target ownership / rejection and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[False] Ownership without CREATE SCHEMA / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[True] Unqualified target / current-database resolution -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_target_database_resolution Transient schema / future grants and properties -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[transient] Managed-access schema / future grants and properties -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[managed] Two sessions / same-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[False] Two sessions / cross-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[True] Quoted dotted schema names / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_dots Over-depth source / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[True] Over-depth target / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[False] Outside dependent / missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[missing] Outside dependent / shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[different_shape] Quoted references and literals / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_view_references IF EXISTS / target privilege precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_if_exists_target_privileges Missing source database / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-False] Missing source database / IF EXISTS error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-False] Missing target database / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-True] Missing target database / IF EXISTS error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-True] Contained mixed-qualified view / same-database unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[unread-False] Contained mixed-qualified view / cross-database unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[unread-True] Contained view and chain / same-database missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[missing-False] Contained view and chain / cross-database missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[missing-True] Contained view and chain / same-database shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[shape-False] Contained view and chain / cross-database shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[shape-True] Self cycle / same-database swap success, read error, and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[self-False] Self cycle / cross-database swap success, read error, and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[self-True] Mutual cycle / same-database member-specific errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[mutual-False] Mutual cycle / cross-database member-specific errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[mutual-True] Two temporary sessions without permanent namesake / same-database reads, outsider errors, and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[False-False] Two temporary sessions without permanent namesake / cross-database reads, outsider errors, and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[False-True] Two temporary sessions with permanent namesake / same-database independent results and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[True-False] Two temporary sessions with permanent namesake / cross-database independent results and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[True-True] Two sessions' temporary dependencies / same-database namesake binding and unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_dependencies[False] Two sessions' temporary dependencies / cross-database namesake binding and unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_dependencies[True] Temporary namesake / same-database logical shape errors and recovery in two sessions -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_expansion_recovery[False] Temporary namesake / cross-database logical shape errors and recovery in two sessions -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_expansion_recovery[True] Quoted dotted identifiers, ordinary strings, dollar strings and comments / same-database round-trip -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_quoted_view_lexical_forms[False] Quoted dotted identifiers, ordinary strings, dollar strings and comments / cross-database round-trip -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_quoted_view_lexical_forms[True] Permanent external cycle dependent / same-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[False-False] Permanent external cycle dependent / cross-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[False-True] Temporary external cycle dependent / same-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[True-False] Temporary external cycle dependent / cross-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[True-True] Permanent transitive shape error / same-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[False-False] Permanent transitive shape error / cross-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[False-True] Temporary transitive shape error / same-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[True-False] Temporary transitive shape error / cross-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[True-True] Legacy nested DROP expansion error / unchanged snapshot -> tests/queries/test_stale_views.py::test_drop_table_dependent_view_chain_goes_stale Schema clone / contained sequence and defaults -> tests/queries/ddl/test_sequence_clone.py::test_schema_clone_rebinds_table_default Schema UNDROP / restored contents -> tests/queries/ddl/test_undrop_schema.py::test_undrop_schema_restores_tables_and_rows Table swap / rows and swap-back -> tests/queries/test_alter_table_swap.py::test_swap_dml_then_swap_back * LAV-3804: investigator: resume repaired tip for review; do not repeat Cloud capture Attempt 1 exhausted wall_clock after steady progress, not a blocked implementation or repeated terminal failure. Initial implementation a087ac4db was reviewed; 1a41df664 then addressed the three added stale-location, cycle, and session-identity ACs. Current content tip 75f46f4fb includes origin/main 08cbbcf1f. git status --porcelain was empty and git diff --check origin/main passed. Evidence: /var/lib/spiral/transcripts/driver.log lines 12-23 records two successful implementation turns, reviewer feedback, and two driver checks phases. The second checks phase ended ok (skipped: the scripts tests) at 2026-10-07T23:24:35Z; wall_clock exhaustion followed at 23:24:37Z, before a fresh reviewer turn. Each driver gate took about 55 minutes. checks.log lines 68-96 records 126 state-snapshot tests passed, 1 skipped, in 2863.98s, and scripts tests deferred because only 16s remained. The first gate had timed out its state-snapshot lane; the second completed it. These were driver-owned gates, not evidence of a forbidden duplicate manual gate. The second implementer transcript records serial Cloud captures and failing repros turning green: 193 regression tests at 22:25:45Z, 28 new schema-swap view cases at 22:27:47Z, and final make check at 22:28:40Z. Clone selection passed 15 tests at 22:22:43Z. /tmp/lav3804-check-handoff.log ends Quick check passed. Existing tests and snapshots have no changes versus origin/main. Read-only verification: schema.rs now includes already-stale views, replans stored definitions at their destination, uses session-aware physical names, and scopes catalog updates with session_id IS NOT DISTINCT FROM the owning session. dependent_views.rs bounds recursive depth traversal with CYCLE. New test_schema_swap_views.py and its 28 Cloud snapshot cases cover consecutive unread swaps, contained expansion recovery, self/mutual cycles, and two-session temporary/permanent identity, in same- and cross-database forms. Next round: resume the repaired tip rather than reimplementing historical reviewer feedback or recapturing unchanged Cloud snapshots. Run make check and focused compat replay if needed after the final main merge or any new edit, then hand off promptly for the driver-owned gate and fresh reviewer assessment. Never run .spiral/check.sh manually or skip/modify its gates. The scripts lane was not verified on this worker and must run through the normal gate/CI; the fixed revision has not yet received a fresh reviewer verdict. No waiver or production-code change is granted by this note. The next dispatch starts from completed captures and committed repairs instead of repeating the original implementation and feedback cycle. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-3804: verify repaired schema swap after main merge Resumed the repaired implementation at 472716a3b as directed by the investigator. Historical reviewer gaps are already addressed by 1a41df664; no source, tests, or snapshots required further changes, and no Cloud captures were repeated. Verification on the merged tip: - make check: passed, including clippy, warnings-as-errors, formatting and vendored sqlparser tests. - make test-compat across test_schema_swap.py, test_schema_swap_views.py, test_schemas.py, test_schema_cross_database_rename.py, test_undrop_schema.py, test_stale_views.py, test_alter_table_swap.py, test_temporary_view_session_scope.py and test_view_row_access_policy.py: 197 passed. - make test-compat across test_clones.py and test_sequence_clone.py with -k schema: 17 passed. - git diff --check origin/main...HEAD: passed; working tree remains clean. The complete implementation test matrix and deviations remain recorded in 1a41df664. Existing tests and snapshots remain unchanged. Driver-owned checks and fresh review are still required; .spiral/check.sh was not run manually. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> * LAV-3804: attribute schema swap view errors to the current reader Address the latest reviewer-gaps feedback. Schema swap now stores a canonical missing-reference payload, not the swapper's primary/secondary-role diagnostic. The named tombstone reader formats that payload using the reading session's current role and secondary-role settings. Its exception hint carries the canonical payload through transitive repair and legacy tombstone naming without persisting a previous reader's formatted context. Existing SQLSTATEs and the three-part repair-location detail remain unchanged. Collect and qualify unqualified lowered relations on the AST, with a stack of query-local CTE scopes, so probing and subsequent repair use the moved view's namespace. Replay existing object grants when either the root repair or the dependent reconstruction drops/recreates a view. No catalog, lock protocol, wire retry policy, or independent-root repair behavior is introduced. Add 16 serially Cloud-captured cases: eight permanent role-transition cases, four temporary role-transition cases, and four nested-CTE/unqualified-reference cases. The permanent tests distinguish view owner, schema swapper and another session's reader, switch NONE/ALL/NONE after the swap, exercise both read orders, and verify preserved reader access after swap-back. Existing test functions and all 28 pre-existing snapshot entries in this file are unchanged. Verification: - Initial Cloud-captured role-transition replay: eight failures on the starting tip, including frozen swapper attribution and lost reconstructed-view grants. - make check: passed, including formatting, clippy, warnings-as-errors, and vendored sqlparser tests. - make test-compat across test_schema_swap.py, test_schema_swap_views.py, test_schemas.py, test_schema_cross_database_rename.py, test_undrop_schema.py, test_stale_views.py, test_alter_table_swap.py, test_temporary_view_session_scope.py, and test_view_row_access_policy.py: 213 passed, including all 44 schema-swap view cases. - make test-compat across test_clones.py and test_sequence_clone.py with PYTEST_ARGS='-k schema': 17 passed. - git diff --check: passed. Structured comparison confirms no existing snapshot entries changed or disappeared; only 16 Cloud-captured entries added. - The driver's .spiral/check.sh was not run manually. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Sweep Swept schema.rs, table.rs, and dependent_views.rs named-tombstone producers and exception-copying paths for session-frozen diagnostics; fixed swap construction, transitive propagation, and legacy naming. Plain expansion, shape, and cycle errors retain their previous messages. Swept lowered unqualified references and both root/dependent rebuild sites; also fixed missing dependency edges, wrong probing namespaces, and grant reprojection. CTE aliases remain scoped and unmodified. Existing ordinary/dollar literals, quoted identifiers, comments, temporary/permanent namesakes, cycles, and two-session cases replay green. Independent stale roots remain the separate LAV-3887 work, unchanged here. ## Deviations The new reader-context captures also exposed incomplete unqualified-reference tracking and lost PG grants after view reconstruction. Normalizing these references on the AST and reusing existing grant reprojection at both rebuild sites are necessary for Cloud-matching transitive diagnostics and swap-back access; neither changes the namespace/catalog/locking architecture. ## Test matrix Resource-index denominator: ALTER SCHEMA [IF EXISTS] <name> SWAP WITH <target>. Other ALTER SCHEMA clauses are unchanged. The existing AC/name/lifecycle matrix is included below; additional reader-context and reference-scope cells follow. Permanent / NONE swapper, direct-first, same DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-False-False] Permanent / NONE swapper, direct-first, cross DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-False-True] Permanent / NONE swapper, transitive-first, same DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-True-False] Permanent / NONE swapper, transitive-first, cross DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-True-True] Permanent / ALL swapper, direct-first, same DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-False-False] Permanent / ALL swapper, direct-first, cross DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-False-True] Permanent / ALL swapper, transitive-first, same DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-True-False] Permanent / ALL swapper, transitive-first, cross DB / direct and transitive errors under ACCOUNTADMIN and another session's reader, NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-True-True] Permanent / NONE swapper, direct-first, same DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-False-False] Permanent / NONE swapper, direct-first, cross DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-False-True] Permanent / NONE swapper, transitive-first, same DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-True-False] Permanent / NONE swapper, transitive-first, cross DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[NONE-True-True] Permanent / ALL swapper, direct-first, same DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-False-False] Permanent / ALL swapper, direct-first, cross DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-False-True] Permanent / ALL swapper, transitive-first, same DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-True-False] Permanent / ALL swapper, transitive-first, cross DB / swap-back rows and preserved reader SELECT grants, NONE/ALL -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_missing_reference_reader_context[ALL-True-True] Temporary / NONE swapper, same DB / direct/transitive errors after changing primary role and NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[NONE-False] Temporary / NONE swapper, cross DB / direct/transitive errors after changing primary role and NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[NONE-True] Temporary / ALL swapper, same DB / direct/transitive errors after changing primary role and NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[ALL-False] Temporary / ALL swapper, cross DB / direct/transitive errors after changing primary role and NONE/ALL/NONE -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[ALL-True] Temporary / NONE swapper, same DB / transitive-first swap-back restores rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[NONE-False] Temporary / NONE swapper, cross DB / transitive-first swap-back restores rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[NONE-True] Temporary / ALL swapper, same DB / transitive-first swap-back restores rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[ALL-False] Temporary / ALL swapper, cross DB / transitive-first swap-back restores rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_missing_reference_reader_context[ALL-True] Unqualified base with nested CTE shadow, missing target / same DB / direct/transitive errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[False-False] Unqualified base with nested CTE shadow, missing target / cross DB / direct/transitive errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[False-True] Unqualified base with nested CTE shadow, populated target / same DB / moved namespace rows and swap-back rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[True-False] Unqualified base with nested CTE shadow, populated target / cross DB / moved namespace rows and swap-back rows -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[True-True] CTE-only view / same DB, no underlying relation / unchanged reads through swaps -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[False-False] CTE-only view / cross DB, no underlying relation / unchanged reads through swaps -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_unqualified_reference_scopes[False-True] Inherited matrix (retained behavior, also replayed this round): Populated schemas / tables, sequences, metadata and grants / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_populated Same target / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-same] Same target / IF EXISTS success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-same] Missing source / error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_source] Missing source / IF EXISTS no-op and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_source] Missing target / error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-missing_target] Missing target / IF EXISTS error and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-missing_target] Both missing / source error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[False-both_missing] Both missing / IF EXISTS target error precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_rejections[True-both_missing] Quoted qualified schemas / same-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[False] Quoted qualified schemas / cross-database success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_qualified[True] Qualified, unqualified, transitive and outside views / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_view_names Missing target ownership / rejection and preservation -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[False] Ownership without CREATE SCHEMA / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_ownership[True] Unqualified target / current-database resolution -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_target_database_resolution Transient schema / future grants and properties -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[transient] Managed-access schema / future grants and properties -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_schema_properties[managed] Two sessions / same-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[False] Two sessions / cross-database namespace slots -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_session_slots[True] Quoted dotted schema names / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_dots Over-depth source / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[True] Over-depth target / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_over_depth[False] Outside dependent / missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[missing] Outside dependent / shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_dependent_errors[different_shape] Quoted references and literals / success -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_quoted_view_references IF EXISTS / target privilege precedence -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_if_exists_target_privileges Missing source database / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-False] Missing source database / IF EXISTS error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-False] Missing target database / error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[False-True] Missing target database / IF EXISTS error -> tests/queries/ddl/test_schema_swap.py::test_schema_swap_missing_database[True-True] Contained mixed-qualified view / same-database unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[unread-False] Contained mixed-qualified view / cross-database unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[unread-True] Contained view and chain / same-database missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[missing-False] Contained view and chain / cross-database missing-reference error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[missing-True] Contained view and chain / same-database shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[shape-False] Contained view and chain / cross-database shape error and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_stale_location[shape-True] Self cycle / same-database swap success, read error, and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[self-False] Self cycle / cross-database swap success, read error, and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[self-True] Mutual cycle / same-database member-specific errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[mutual-False] Mutual cycle / cross-database member-specific errors and swap-back recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_view_cycle[mutual-True] Two temporary sessions without permanent namesake / same-database reads, outsider errors, and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[False-False] Two temporary sessions without permanent namesake / cross-database reads, outsider errors, and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[False-True] Two temporary sessions with permanent namesake / same-database independent results and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[True-False] Two temporary sessions with permanent namesake / cross-database independent results and swap-back -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_view_identity[True-True] Two sessions' temporary dependencies / same-database namesake binding and unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_dependencies[False] Two sessions' temporary dependencies / cross-database namesake binding and unread double-swap recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_dependencies[True] Temporary namesake / same-database logical shape errors and recovery in two sessions -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_expansion_recovery[False] Temporary namesake / cross-database logical shape errors and recovery in two sessions -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_temporary_expansion_recovery[True] Quoted dotted identifiers, ordinary strings, dollar strings and comments / same-database round-trip -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_quoted_view_lexical_forms[False] Quoted dotted identifiers, ordinary strings, dollar strings and comments / cross-database round-trip -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_quoted_view_lexical_forms[True] Permanent external cycle dependent / same-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[False-False] Permanent external cycle dependent / cross-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[False-True] Temporary external cycle dependent / same-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[True-False] Temporary external cycle dependent / cross-database exact cycle errors and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_external_cycle_dependent[True-True] Permanent transitive shape error / same-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[False-False] Permanent transitive shape error / cross-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[False-True] Temporary transitive shape error / same-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[True-False] Temporary transitive shape error / cross-database exact dependency error and recovery -> tests/queries/ddl/test_schema_swap_views.py::test_schema_swap_transitive_shape_error[True-True] Legacy nested DROP expansion error / unchanged snapshot -> tests/queries/test_stale_views.py::test_drop_table_dependent_view_chain_goes_stale Schema clone / contained sequence and defaults -> tests/queries/ddl/test_sequence_clone.py::test_schema_clone_rebinds_table_default Schema UNDROP / restored contents -> tests/queries/ddl/test_undrop_schema.py::test_undrop_schema_restores_tables_and_rows Table swap / rows and swap-back -> tests/queries/test_alter_table_swap.py::test_swap_dml_then_swap_back --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud>
1 parent 8b00692 commit ecea80e

3 files changed

Lines changed: 12 additions & 1 deletion

File tree

‎src/ast/ddl.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5501,6 +5501,11 @@ pub enum AlterSchemaOperation {
55015501
/// The new name for the schema.
55025502
name: ObjectName,
55035503
},
5504+
/// Exchange the schema with another schema.
5505+
SwapWith {
5506+
/// The schema to exchange with.
5507+
name: ObjectName,
5508+
},
55045509
/// Change the owner of the schema.
55055510
OwnerTo {
55065511
/// The new owner of the schema.
@@ -5526,6 +5531,7 @@ impl fmt::Display for AlterSchemaOperation {
55265531
write!(f, "SET OPTIONS ({})", display_comma_separated(options))
55275532
}
55285533
AlterSchemaOperation::Rename { name } => write!(f, "RENAME TO {name}"),
5534+
AlterSchemaOperation::SwapWith { name } => write!(f, "SWAP WITH {name}"),
55295535
AlterSchemaOperation::OwnerTo { owner } => write!(f, "OWNER TO {owner}"),
55305536
}
55315537
}

‎src/ast/spans.rs‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2841,7 +2841,9 @@ impl Spanned for AlterSchemaOperation {
28412841
AlterSchemaOperation::SetOptionsParens { options } => {
28422842
union_spans(options.iter().map(|i| i.span()))
28432843
}
2844-
AlterSchemaOperation::Rename { name } => name.span(),
2844+
AlterSchemaOperation::Rename { name } | AlterSchemaOperation::SwapWith { name } => {
2845+
name.span()
2846+
}
28452847
AlterSchemaOperation::OwnerTo { owner } => {
28462848
if let Owner::Ident(ident) = owner {
28472849
ident.span

‎src/parser/mod.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13946,6 +13946,9 @@ impl<'a> Parser<'a> {
1394613946
} else if self.parse_keywords(&[Keyword::RENAME, Keyword::TO]) {
1394713947
let new_name = self.parse_object_name(false)?;
1394813948
AlterSchemaOperation::Rename { name: new_name }
13949+
} else if self.parse_keywords(&[Keyword::SWAP, Keyword::WITH]) {
13950+
let name = self.parse_object_name(false)?;
13951+
AlterSchemaOperation::SwapWith { name }
1394913952
} else if self.parse_keywords(&[Keyword::OWNER, Keyword::TO]) {
1395013953
let owner = self.parse_owner()?;
1395113954
AlterSchemaOperation::OwnerTo { owner }

0 commit comments

Comments
 (0)