clockworklabs/SpacetimeDB · error

adding non-unique constraints is not supported (constraint o

Error message

adding non-unique constraints is not supported (constraint on table {table_id})

What it means

Only unique constraints are supported by the datastore today. create_constraint extracts the constrained columns via constraint.data.unique_columns(); if the schema describes any other kind of constraint, it is rejected up front, before any system-table mutation, with the table id in the message.

Source

Thrown at crates/datastore/src/locking_tx_datastore/mut_tx.rs:2340

        Ok((table_id, schema))
    }

    /// Creates a constraint, making the corresponding indices unique.
    ///
    /// This inserts constraint metadata AND converts the in-memory indices
    /// from non-unique to unique. If the existing data contains duplicate
    /// values in the constrained columns, an error is returned.
    ///
    /// Pre-validation (before any system-table mutation):
    /// - the constraint must be a unique one (the only kind supported today);
    /// - the target table must already have at least one index on the constrained columns.
    pub fn create_constraint(&mut self, constraint: ConstraintSchema) -> Result<ConstraintId> {
        let table_id = constraint.table_id;

        // (a) Only unique constraints are supported at the moment. Reject anything else
        //     up front, before writing to `st_constraint`.
        let Some(cols) = constraint.data.unique_columns().cloned() else {
            return Err(anyhow::anyhow!(
                "adding non-unique constraints is not supported (constraint on table {table_id})"
            )
            .into());
        };
        let col_list: ColList = cols.into();

        // (b) A unique constraint must be backed by at least one index on the same columns.
        //     Check on the committed table; the tx table's index set is kept in lockstep
        //     with the committed one by the datastore, so agreement is an invariant.
        {
            let (_, (commit_table, _, _)) = self.get_or_create_insert_table_mut(table_id)?;
            if commit_table.get_indexes_by_cols(&col_list).is_empty() {
                return Err(anyhow::anyhow!(
                    "unique constraint on table {table_id} column(s) {col_list:?} \
                     requires at least one backing index on those columns"
                )
                .into());
            }

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. Model only unique constraints; enforce other kinds (checks, FKs) in module reducers or application logic
  2. Filter unsupported constraint kinds out of migration/DDL translation before they reach create_constraint
  3. Track the SpacetimeDB roadmap for non-unique constraint support
Defensive patterns

Strategy: validation

Validate before calling

// Filter to the supported constraint kind before calling the datastore
fn is_supported_constraint(schema: &ConstraintSchema) -> bool {
    schema.data.unique_columns().is_some()
}

if !is_supported_constraint(&schema) {
    return Err(anyhow::anyhow!("only unique constraints are supported; enforce other rules in reducers"));
}

Type guard

fn is_unique_constraint(schema: &ConstraintSchema) -> bool {
    schema.data.unique_columns().is_some()
}

Try / catch

match tx.create_constraint(schema) {
    Err(e) if e.to_string().contains("adding non-unique constraints is not supported") => {
        // Drop the unsupported constraint or reimplement it as reducer-level validation
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling MutTxId::create_constraint with a ConstraintData variant that carries no unique columns - e.g. a check-style or foreign-key-style constraint schema.

Common situations: Tooling or SQL layers that translate general SQL constraints; forward-ported schema dumps; tests exercising unsupported constraint kinds.

Related errors


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