clockworklabs/SpacetimeDB · error
Invalid number of tables in subscription: {}
Error message
Invalid number of tables in subscription: {} What it means
Subscription fragment compilation (Fragments::compile_from_plan) only implements single-table and two-table join plans: the match covers exactly [dr] and [dr, ds] and bails for any other count. Subscriptions joining three or more tables are therefore unsupported.
Source
Thrown at crates/subscription/src/lib.rs:236
)?,
new_plan(
// R'ds(-)
plan,
&[(*ds, Delta::Deletes)],
)?,
new_plan(
// dr(+)ds(+)
plan,
&[(*dr, Delta::Inserts), (*ds, Delta::Inserts)],
)?,
new_plan(
// dr(-)ds(-)
plan,
&[(*dr, Delta::Deletes), (*ds, Delta::Deletes)],
)?,
],
}),
_ => bail!("Invalid number of tables in subscription: {}", tables.len()),
}
}
}
/// A join edge is used for pruning queries when evaluating subscription updates.
///
/// If we have the following subscriptions:
/// ```sql
/// SELECT a.* FROM a JOIN b ON a.id = b.id WHERE b.x = 1
/// SELECT a.* FROM a JOIN b ON a.id = b.id WHERE b.x = 2
/// ...
/// SELECT a.* FROM a JOIN b ON a.id = b.id WHERE b.x = n
/// ```
///
/// Whenever `a` is updated, only the relevant queries are evaluated.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct JoinEdge {
/// The [`TableId`] for `a`View on GitHub (pinned to 6dee26c6ef)
Solutions
- Reduce each subscription to at most two tables: split the multi-join into several subscriptions and stitch results together client-side.
- Denormalize: maintain a join-result table updated by reducers and subscribe to that single table.
- Move the extra lookup into module logic (reducer-written helper columns) so the client query needs fewer joins.
Example fix
-- before: three-table join SELECT a.* FROM a JOIN b ON a.b_id = b.id JOIN c ON b.c_id = c.id; -- after: two subscriptions joined client-side, or a denormalized table SELECT * FROM a JOIN b ON a.b_id = b.id; SELECT * FROM c; -- better: reducer-maintained `a_b_c` table, subscribe to it alone
Defensive patterns
Strategy: validation
Validate before calling
-- count FROM/JOIN tables in every subscription before shipping; max is 2: -- SELECT a.* FROM a JOIN b ON ...; -- ok (2 tables) -- SELECT ... FROM a JOIN b JOIN c; -- will fail: 3 tables
Try / catch
try {
await db.subscription.build([sql]).subscribe();
} catch (e: any) {
if (String(e.message).includes("Invalid number of tables in subscription")) {
// split into <=2-table subscriptions, or denormalize into one table
}
} Prevention
- Keep subscriptions to single tables or one join; combine results client-side.
- Denormalize multi-entity reads into reducer-maintained tables.
- Lint generated subscription SQL for join count in CI.
When it happens
Trigger: Subscribing to a SELECT whose FROM/JOIN involves 3 or more tables, producing a tables slice with length > 2 (or 0).
Common situations: Widening a working two-table join subscription with one more lookup table; porting analytical multi-join SQL into subscriptions.
Related errors
- SQL query exceeds maximum allowed length: \"{sql:.120}...\"
- Subscriptions require indexes on join columns
- Subscriptions must have at least one query
- Subscriptions must be SQL strings or typed queries
- Unsubscribe has already been called
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/6ad48de3fc8a493c.
Report an issue: GitHub.