clockworklabs/SpacetimeDB · critical

table should exist in the database for AddConstraint

Error message

table should exist in the database for AddConstraint

What it means

Panic during SpacetimeDB automatic database migration. For an `AutoMigrateStep::AddConstraint` step, the migration plan says a constraint is being added to an existing table; the code looks the table up in the live database with `table_id_from_name_mut(tx, &table_full_name)` and `.expect("table should exist in the database for AddConstraint")`. Panic means the plan referenced a table name that the database does not contain — an invariant break between the computed migration plan and actual DB state.

Source

Thrown at crates/engine/src/update.rs:486

                log!(
                    logger,
                    "Dropping constraint `{constraint_name}` on table `{table_full_name}`"
                );
                stdb.drop_constraint(tx, constraint_schema.constraint_id)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddConstraint(key) => {
                let (namespace, constraint_name) = key;
                let (owning_def, table_def) = plan
                    .new
                    .find_storing_table(namespace, constraint_name)
                    .ok_or_else(|| anyhow::anyhow!("AddConstraint: `{constraint_name}` not found in new module def"))?;
                let table_full_name = joined(namespace, &table_def.name);
                let constraint_def: &ConstraintDef = plan.new.lookup(key).ok_or_else(|| {
                    anyhow::anyhow!("AddConstraint: constraint `{constraint_name}` not found in new module def")
                })?;
                let table_id = stdb
                    .table_id_from_name_mut(tx, &table_full_name)?
                    .expect("table should exist in the database for AddConstraint");
                let mut constraint_schema =
                    ConstraintSchema::from_module_def(owning_def, constraint_def, table_id, ConstraintId::SENTINEL);

                // Apply namespace prefix for submodule constraints
                constraint_schema.constraint_name = namespace.join_raw(&constraint_schema.constraint_name);
                log!(
                    logger,
                    "Adding constraint `{constraint_name}` on table `{table_full_name}`"
                );
                stdb.create_constraint(tx, constraint_schema)?;
            }
            spacetimedb_schema::auto_migrate::AutoMigrateStep::AddSequence(key) => {
                let (namespace, sequence_name) = key;
                let (owning_def, table_def) = plan
                    .new
                    .find_storing_table(namespace, sequence_name)
                    .ok_or_else(|| anyhow::anyhow!("AddSequence: `{sequence_name}` not found in new module def"))?;
                let table_full_name = joined(namespace, &table_def.name);

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Check whether the table was renamed or moved — if so, a rename/migration step must precede AddConstraint or you must publish as a breaking change (drop & recreate database).
  2. Verify the constraint and table names printed in the error (`Adding constraint \`X\` on table \`Y\``) against the actual database schema (`spacetime sql <db> 'SELECT * FROM st_table'` style introspection).
  3. If the database is disposable (dev), delete it and publish fresh instead of migrating.
  4. If names match visually, suspect namespace-join differences (submodule prefixing) — report upstream with the module and its previous version, since this is an internal plan/DB mismatch.

Example fix

# before: rename table AND add constraint in one publish -> plan adds constraint to a table name not in DB
spacetime publish mydb --project mod2

# after: publish the rename first (auto-migrate handles it), then add the constraint in a second publish
spacetime publish mydb --project rename-only
spacetime publish mydb --project with-constraint
Defensive patterns

Strategy: validation

Validate before calling

// Before migrating, confirm every constrained table exists under its joined name
for (_, t) in plan.new.tables() {
    if stdb.table_id_from_name_mut(tx, &joined(ns, &t.name))?.is_none() {
        return Err(anyhow!("table `{}` missing; migration plan invalid", t.name));
    }
}

Try / catch

let r = std::panic::catch_unwind(AssertUnwindSafe(|| stdb.apply_auto_migrate(tx, plan)));
match r { Ok(res) => res?, Err(_) => return Err(MigrationFailed::db_state_mismatch) }

Prevention

When it happens

Trigger: Publishing an updated module whose auto-migration plan includes `AddConstraint` (e.g. adding a unique/index constraint) on a table whose fully-qualified name (namespace-qualified, submodules joined) is not present in the running database. This can happen when the table was created under a different name/path, the plan's namespace-joining logic produces a different string than what was stored, or the database state is stale/corrupted.

Common situations: Renaming tables or moving them between submodules while also adding constraints in the same publish; publishing against a database created by a much older SpacetimeDB version where table names were stored unprefixed; interrupted earlier migrations leaving partial state; mixing manual DDL with module-driven schema.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/ce40713f56ec8f1f. Report an issue: GitHub.