clockworklabs/SpacetimeDB · error

table {} not found in old_module_def

Error message

table {} not found in old_module_def

What it means

During update_database, every existing non-system, non-view table in the database must be found by full prefix-qualified name in the old module def used to build the migration plan. The error means the database contains a user table that the supplied old module def does not declare — the def and the actual database schema have drifted apart.

Source

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

    // Build a map from full-name (namespaced) -> (owning_def, table_def) covering root and all
    // submodule tables. Submodule tables are stored in the DB with prefixed names like
    // "lib.library_procedure_timer", but `ModuleDef::table()` only has the current level.
    // `all_tables_with_prefix()` returns the owning submodule alongside each def, which is also
    // needed so that `check_compatible` resolves column type refs against the correct
    // (sub)module typespace.
    let old_tables_by_name: std::collections::HashMap<String, _> = old_module_def
        .all_tables_with_prefix()
        .into_iter()
        .map(|(prefix, owning_def, table_def)| (format!("{}{}", prefix, &table_def.name[..]), (owning_def, table_def)))
        .collect();

    for table in existing_tables
        .iter()
        .filter(|table| table.table_type != StTableType::System && !table.is_view())
    {
        let (owning_def, old_def) = old_tables_by_name
            .get(table.table_name.as_ref())
            .ok_or_else(|| anyhow::anyhow!("table {} not found in old_module_def", table.table_name))?;

        table.check_compatible(owning_def, old_def)?;
    }

    match plan {
        MigratePlan::Manual(plan) => manual_migrate_database(stdb, tx, plan, logger),
        MigratePlan::Auto(plan) => auto_migrate_database(stdb, tx, auth_ctx, plan, logger),
    }
}

/// Manually migrate a database.
fn manual_migrate_database(
    _stdb: &RelationalDB,
    _tx: &mut MutTxId,
    _plan: ManualMigratePlan,
    _logger: &dyn UpdateLogger,
) -> anyhow::Result<UpdateResult> {
    unimplemented!("Manual database migrations are not yet implemented")

View on GitHub (pinned to 6dee26c6ef)

Solutions

  1. Republish the exact module currently initialized on the database so the old def matches reality before migrating
  2. Check the failing table's full name for submodule-prefix issues (lib.<submodule>.<table>) on both sides
  3. If the database is disposable, publish to a fresh database instead of migrating drifted state

Example fix

// before: plan built from a stale cached def
let plan = build_plan(cached_old_def, new_def);
update_database(&stdb, &mut tx, auth, plan, &logger)?;

// after: verify the def covers the DB before planning
let existing: Vec<String> = stdb.get_all_tables_mut(&mut tx)?
    .iter().filter(|t| t.table_type != StTableType::System && !t.is_view())
    .map(|t| t.table_name.to_string()).collect();
for name in &existing {
    anyhow::ensure!(old_tables_by_name.contains_key(name), "table {name} missing from old def");
}
Defensive patterns

Strategy: validation

Validate before calling

// Verify every user table in the DB is declared by the old def before migrating
let declared: HashSet<String> = old_module_def
    .all_tables_with_prefix()
    .into_iter()
    .map(|(prefix, _, t)| format!("{}{}", prefix, &t.name[..]))
    .collect();
for t in stdb.get_all_tables_mut(&mut tx)? {
    if t.table_type != StTableType::System && !t.is_view() {
        anyhow::ensure!(declared.contains(t.table_name.as_ref()),
            "table {} not covered by old def", t.table_name);
    }
}

Type guard

fn table_is_module_managed(t: &TableSchema) -> bool {
    t.table_type != StTableType::System && !t.is_view()
}

Try / catch

match update_database(&stdb, &mut tx, auth, plan, &logger) {
    Err(e) if e.to_string().contains("not found in old_module_def") => {
        // def/DB drift: refresh the old def or republish fresh; do not retry the same plan
        anyhow::bail!("migration aborted; old def does not describe the database: {e:#}");
    }
    r => r,
}

Prevention

When it happens

Trigger: Auto-migrating with an old module def that does not match the module actually published on the database; submodule table-name prefix mismatches (stored names look like lib.<submodule>.<table> while the def only has the local name); tables created outside the module (raw SQL DDL) that no module def covers.

Common situations: Version skew between a host's cached module def and the DB; republishing after submodule renames; mixing manual SQL DDL tables with module tables in one database.

Related errors


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