Skip to content

adapter: fail cleanly when a staged CREATE races with DROP ... CASCADE#37650

Draft
ggevay wants to merge 1 commit into
MaterializeInc:mainfrom
ggevay:gabor/sql-518-create-secret-dropped-database
Draft

adapter: fail cleanly when a staged CREATE races with DROP ... CASCADE#37650
ggevay wants to merge 1 commit into
MaterializeInc:mainfrom
ggevay:gabor/sql-518-create-secret-dropped-database

Conversation

@ggevay

@ggevay ggevay commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Fixes SQL-518

Motivation

Nightly Parallel Workload hit a coordinator panic (OrdMap::index: invalid key in CatalogState::get_database, reached via resolve_full_name in Catalog::transact_op). CREATE SECRET is staged: its ensure stage writes the secret to the secret store in an off-thread task, and only the finish stage transacts the catalog. CREATE SECRET is exempt from DDL serialization, so a concurrent DROP DATABASE ... CASCADE can commit inside that window. The finishing Op::CreateItem then names a schema in the dropped database and the coordinator panics resolving the new item's full name. The CREATE SECRET PlanValidity tracks no dependencies, so nothing catches the drop before the transaction. The same window exists for DROP SCHEMA ... CASCADE (via the schema lookup one step later) and for every other statement that reaches Op::CreateItem without holding the DDL lock: CREATE CONNECTION (off-thread validation) and CREATE SOURCE / SINK / TABLE FROM SOURCE (off-thread purification).

Description

User-facing change: a staged CREATE statement (CREATE SECRET, CREATE CONNECTION, CREATE SOURCE, CREATE SINK, CREATE TABLE ... FROM SOURCE) that races with a concurrent DROP DATABASE ... CASCADE or DROP SCHEMA ... CASCADE of its target now returns a clean database 'uN' was dropped / schema 'uN' was dropped error instead of crashing environmentd.

The fix is at the transact_op level rather than in PlanValidity, so one check covers the whole statement class. Op::CreateItem now revalidates that the item's target database and schema still exist and returns AdapterError::ConcurrentDependencyDrop otherwise, consistent with the error PlanValidity raises for concurrently dropped dependencies. The check must sit before insert_user_item: the durable catalog performs no referential validation, so without it a fallible name lookup would durably commit an item under a dangling schema id (previously only the panic prevented that).

Also adds a create_secret_between_ensure_and_finish failpoint (mirroring the existing rotate-keys one) and a deterministic regression workflow create-secret-db-drop-race in test/race-condition, wired into Nightly as a new step race-condition-create-secret-db-drop.

Verification

  • Pre-fix, the new race-condition workflow reproduces the exact CI panic (same message and stack); post-fix it passes: CREATE SECRET fails with database 'u2' was dropped, the coordinator survives, and the sanity restart finds no panics.
  • New unit test test_create_item_into_dropped_database_or_schema exercises the transact_op check directly for both the missing-database and missing-schema cases.
  • catalog::transact and coord::validity unit test suites, plus test/sqllogictest/secret.slt and schemas.slt, pass locally.

🤖 Generated with Claude Code

Fixes a coordinator panic (SQL-518) seen in Parallel Workload: CREATE
SECRET is staged, and its ensure stage writes the secret to the secret
store in an off-thread task. CREATE SECRET is exempt from DDL
serialization, so a concurrent DROP DATABASE ... CASCADE can commit
inside that window. The finish stage then runs a catalog transaction
whose Op::CreateItem names a schema in the dropped database, and
transact_op panicked resolving the new item's full name against the
missing database (OrdMap::index: invalid key in
CatalogState::get_database). The CREATE SECRET PlanValidity tracks no
dependencies, so nothing caught the drop before the transaction.

Fix at the transact_op level rather than in PlanValidity, so it covers
every statement that can reach Op::CreateItem with a stale name: CREATE
SECRET, CREATE CONNECTION (off-thread validation), and CREATE SOURCE /
SINK / TABLE FROM SOURCE (off-thread purification). Op::CreateItem now
revalidates that the item's target database and schema still exist and
returns AdapterError::ConcurrentDependencyDrop otherwise. This also
guards insert_user_item, which would happily write an item under a
dangling schema id if name resolution didn't panic first, and covers
the analogous dropped-schema case one line later in resolve_full_name.

Testing:
* a create_secret_between_ensure_and_finish failpoint plus a
  race-condition workflow (create-secret-db-drop-race, wired into
  Nightly) that deterministically reproduces the original panic and now
  verifies the clean error,
* a unit test for the new transact_op check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ggevay ggevay added the A-ADAPTER Topics related to the ADAPTER layer label Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-ADAPTER Topics related to the ADAPTER layer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant