{"record":{"id":"5f3a2686cc3f3a95","repo":"clockworklabs/SpacetimeDB","slug":"failed-to-determine-tableid-for-query","errorCode":null,"errorMessage":"Failed to determine TableId for query","messagePattern":"Failed to determine TableId for query","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/query/src/lib.rs","lineNumber":38,"sourceCode":"\n/// DIRTY HACK ALERT: Maximum allowed length, in UTF-8 bytes, of SQL queries.\n/// Any query longer than this will be rejected.\n/// This prevents a stack overflow when compiling queries with deeply-nested `AND` and `OR` conditions.\nconst MAX_SQL_LENGTH: usize = 50_000;\n\npub fn compile_subscription(\n    sql: &str,\n    tx: &impl SchemaView,\n    auth: &AuthCtx,\n) -> Result<(Vec<ProjectPlan>, TableId, TableName, bool)> {\n    if sql.len() > MAX_SQL_LENGTH {\n        bail!(\"SQL query exceeds maximum allowed length: \\\"{sql:.120}...\\\"\")\n    }\n\n    let (plan, mut has_param) = parse_and_type_sub(sql, tx, auth)?;\n\n    let Some(return_id) = plan.return_table_id() else {\n        bail!(\"Failed to determine TableId for query\")\n    };\n\n    let Some(return_name) = tx.schema_for_table(return_id).map(|schema| schema.table_name.clone()) else {\n        bail!(\"TableId `{return_id}` does not exist\")\n    };\n\n    // Resolve any RLS filters\n    let plan_fragments = resolve_views_for_sub(tx, plan, auth, &mut has_param)?\n        .into_iter()\n        .map(compile_select)\n        .collect::<Vec<_>>();\n\n    // Does this subscription read from a client-specific view?\n    // If so, it is as if the view is parameterized by `:sender`.\n    // We must know this in order to generate the correct query hash.\n    let reads_view = plan_fragments.iter().any(|plan| plan.reads_from_view(false));\n\n    Ok((plan_fragments, return_id, return_name, has_param || reads_view))","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/query/src/lib.rs#L20-L56","documentation":"A subscription must return rows of a single identifiable table so the server can compute insert/delete deltas. After parsing and type checking, compile_subscription calls plan.return_table_id(); if the projection does not resolve to a table's row type, the id is None and compilation fails.","triggerScenarios":"Subscribing to a query whose return type is not a table row: pure aggregates (SELECT COUNT(*) ...), scalar-only projections, or result shapes with no backing table.","commonSituations":"Trying to use subscriptions as continuous aggregates or live counters; selecting only computed expressions; porting ad-hoc SQL queries into subscription calls.","solutions":["Rewrite the subscription to return table rows, e.g. SELECT * FROM t WHERE ....","For aggregates, maintain a summary table updated by reducers and subscribe to that table.","Compute derived values client-side from the subscribed rows."],"exampleFix":"-- before: aggregate return type has no TableId\nSELECT COUNT(*) FROM events;\n\n-- after: subscribe to rows (or to a reducer-maintained summary table)\nSELECT * FROM events;\n-- or: SELECT * FROM event_stats;","handlingStrategy":"validation","validationCode":"-- validate the shape before subscribing: the query must return rows of one table\n-- SELECT * FROM t;          -- ok\n-- SELECT t.* FROM t JOIN ..; -- ok (row type of t)\n-- SELECT COUNT(*) FROM t;    -- will fail: no TableId","typeGuard":null,"tryCatchPattern":"try {\n  await db.subscription.build([\"SELECT COUNT(*) FROM orders\"]).subscribe();\n} catch (e: any) {\n  if (String(e.message).includes(\"Failed to determine TableId\")) {\n    // subscribe to table rows and aggregate client-side\n  }\n}","preventionTips":["Make every subscription return table rows (SELECT * / SELECT t.*).","Compute aggregates in reducers writing summary tables; subscribe to those.","Validate subscription SQL in integration tests before shipping clients."],"tags":["subscription","query","table-id","projection"],"backgroundTag":"invalid-subscription-query","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}