clockworklabs/SpacetimeDB · error

ERROR: Table '%s' not found for multi-column index '%s'

Error message

ERROR: Table '%s' not found for multi-column index '%s'

What it means

V9Builder::AddMultiColumnIndex first resolves the target table by name via findTableByName; when no registered RawTableDefV9 has that exact name, it prints this error and returns, so the composite btree index is never created. The module still publishes — the performance/lookup characteristics are simply missing, which usually surfaces later as full-table scans.

Source

Thrown at crates/bindings-cpp/include/spacetimedb/internal/v9_builder.h:534

        table->sequences.push_back(std::move(seq_def));
        
        //fprintf(stdout, "DEBUG: Added AutoInc sequence for %s.%s\n", 
        //        table_name.c_str(), field_name.c_str());
    }
}

// Template implementation for AddMultiColumnIndex
template<typename T>
void V9Builder::AddMultiColumnIndex(const std::string& table_name,
                                    const std::string& index_name,
                                    const std::vector<std::string>& field_names) {
    //fprintf(stdout, "DEBUG: Adding multi-column index '%s' to table '%s' with %zu fields\n", 
    //        index_name.c_str(), table_name.c_str(), field_names.size());
    
    // Find the existing table
    RawTableDefV9* table = findTableByName(table_name);
    if (!table) {
        fprintf(stderr, "ERROR: Table '%s' not found for multi-column index '%s'\n",
                table_name.c_str(), index_name.c_str());
        return;
    }
    
    // Get field descriptors to find the field index
    SpacetimeDB::field_registrar<T>::register_fields();
    auto& descriptor_map = SpacetimeDB::get_table_descriptors();
    auto it = descriptor_map.find(&typeid(T));
    if (it == descriptor_map.end()) {
        fprintf(stderr, "ERROR: No field descriptors found for table %s\n", table_name.c_str());
        return;
    }
    
    const auto& field_descs = it->second.fields;
    std::vector<uint16_t> field_indexes;
    
    // Find field indices for each field name
    for (const std::string& field_name : field_names) {

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Make the table name string in the multi-index annotation byte-identical to the table macro's name (case-sensitive)
  2. Keep the table registration and its multi-column index in the same translation unit to avoid static-init ordering problems
  3. Search the module for duplicate/overwriting table registrations of the same name
  4. After republishing, inspect the table's index list to confirm the composite index exists

Example fix

// before
ST_TABLE(messages, ...);
ST_MULTI_INDEX(msgs, by_sender_room, sender_id, room_id);  // wrong table name
// after
ST_MULTI_INDEX(messages, by_sender_room, sender_id, room_id);
Defensive patterns

Strategy: validation

Validate before calling

#include <spacetimedb/internal/v9_builder.h>
bool TableIsRegistered(const std::string& name) {
    for (const auto& t : SpacetimeDB::Internal::GetV9Module().tables)
        if (t.name == name) return true;
    return false;
}

Prevention

When it happens

Trigger: The multi-column index annotation names a table that is not registered under that exact string: typo or casing mismatch versus the table macro, registration ordering across translation units (index macro instantiated before the table macro's static object), or a table rename that missed the index annotation.

Common situations: Renaming tables during a schema refactor; keeping index declarations in a separate file that initializes first; copy-pasting index macros between modules; mixing v9-style and v10-style macros so the table lands in a different builder.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/9a4e264347bc7dc9. Report an issue: GitHub.