clockworklabs/SpacetimeDB · error · syn::Error
a direct index must be paired with a `#[unique] constraint
Error message
a direct index must be paired with a `#[unique] constraint
What it means
A `direct` index in SpacetimeDB maps each indexed value to at most one row, so it is only meaningful on a column that is also declared `#[unique]`. During index validation, an `IndexType::Direct` found on a column where `is_unique` is false is rejected at that column's span. Non-unique columns should use `btree` or `hash` indexes.
Source
Thrown at crates/bindings-macro/src/table.rs:416
Ok(IndexArg::new(accessor, kind, None))
}
fn validate<'a>(&'a self, table_name: &str, cols: &'a [Column<'a>]) -> syn::Result<ValidatedIndex<'a>> {
let find_column = |ident| find_column(cols, ident);
let (kind, kind_str) = match &self.kind {
IndexType::BTree { columns } => {
let cols = columns.iter().map(find_column).collect::<syn::Result<Vec<_>>>()?;
(ValidatedIndexType::BTree { cols }, "btree")
}
IndexType::Hash { columns } => {
let cols = columns.iter().map(find_column).collect::<syn::Result<Vec<_>>>()?;
(ValidatedIndexType::Hash { cols }, "hash")
}
IndexType::Direct { column } => {
let col = find_column(column)?;
if !self.is_unique {
return Err(syn::Error::new(
column.span(),
"a direct index must be paired with a `#[unique] constraint",
));
}
(ValidatedIndexType::Direct { col }, "direct")
}
};
let gen_index_name = || {
// See crates/schema/src/validate/v9.rs for the format of index names.
// It's slightly unnerving that we just trust that component to generate this format correctly,
// but what can you do.
let cols = kind.columns();
let cols = cols.iter().map(|col| col.ident.to_string()).collect::<Vec<_>>();
let cols = cols.join("_");
format!("{table_name}_{cols}_idx_{kind_str}")
};
View on GitHub (pinned to 524b4487d9)
Solutions
- Add `#[unique]` to the column the direct index targets
- If the column's values are not actually unique, switch the index to `btree` or `hash`
- Verify existing data satisfies uniqueness before publishing, otherwise inserts will fail at runtime
Example fix
// before
#[spacetimedb::table(accessor = user)]
struct User {
#[index(direct)]
email: String,
}
// after
#[spacetimedb::table(accessor = user)]
struct User {
#[unique]
#[index(direct)]
email: String,
} Defensive patterns
Strategy: validation
Validate before calling
# every direct index must sit on a #[unique] column — review each hit grep -rn 'direct' src/ | grep -v unique || echo OK
Prevention
- Treat `direct` as shorthand for a unique index: always pair it with #[unique]
- Default to btree or hash; reach for direct only for value-to-row lookups
- After adding indexes, read the build output's index list to confirm the intended constraints
When it happens
Trigger: Declaring a direct index — table-level `index = direct(col)` or a column-level direct index — on a column that does not carry `#[unique]`.
Common situations: Switching an index from btree/hash to direct without adding the uniqueness constraint; assuming 'direct' is a generic faster index type rather than a unique value-to-row index.
Related errors
- not a column of the table
- unions not supported
- must specify table accessor, e.g. `#[spacetimedb::table(acce
- spacetimedb table must be a struct
- invalid combination: auto_inc, unique index or primary key c
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/15756bd8cbceef48.
Report an issue: GitHub.