clockworklabs/SpacetimeDB · error · anyhow::Error

query returns `{}` but view expects `{}`

Error message

query returns `{}` but view expects `{}`

What it means

For each plan, the host compares the row type of the source table the query returns against the view's expected row type. If column count, order, or types differ, view creation is rejected and both row types are printed so the exact mismatch is visible. The comparison is on the full product type, not just column names.

Source

Thrown at crates/core/src/host/wasm_common/module_host_actor.rs:200

    let schema_view = SchemaViewer::new(&*tx, &auth);

    // Compile to subscription plans.
    let (plans, has_params) = SubscriptionPlan::compile(the_query, &schema_view, &auth)?;
    ensure!(
        !has_params,
        "parameterized SQL is not supported for view materialization yet"
    );

    // Validate shape and disallow views-on-views.
    for plan in &plans {
        let Some(source_schema) = plan.return_table() else {
            bail!("query does not return plain table rows");
        };
        if plan.reads_from_view(true) || plan.reads_from_view(false) {
            bail!("view definition cannot read from other views");
        }
        if source_schema.row_type != *expected_row_type {
            bail!(
                "query returns `{}` but view expects `{}`",
                fmt_algebraic_type(&AlgebraicType::Product(source_schema.row_type.clone())),
                fmt_algebraic_type(&AlgebraicType::Product(expected_row_type.clone())),
            );
        }
    }

    let op = FuncCallType::View(call_info.clone());
    let mut metrics = ExecutionMetrics::default();
    let mut rows = Vec::new();

    let params = ExecutionParams::from_auth(&auth);

    for plan in plans {
        // Track read sets for all tables involved in this plan.
        // TODO(jsdt): This means we will rerun the view and query for any change to these tables, so we should optimize this asap.
        for table_id in plan.table_ids() {
            tx.record_table_scan(&op, table_id);

View on GitHub (pinned to 9e0d92412f)

Solutions

  1. If the view should mirror the whole row, use SELECT * FROM t
  2. Otherwise list exactly the columns, in order and with matching types, of the expected row type shown in the error message
  3. After altering a table, recreate dependent views to match the new schema
  4. Diff the two row types printed in the message to find the offending column

Example fix

-- before: subset drifts from the expected row (id, name, email)
CREATE VIEW v AS SELECT name, email FROM users;

-- after: match the row type exactly
CREATE VIEW v AS SELECT id, name, email FROM users;
Defensive patterns

Strategy: validation

Validate before calling

-- confirm the source row shape before writing the view
spacetime sql my-db 'SELECT * FROM users LIMIT 0';  -- shows exact columns/order
-- then mirror exactly those columns (and order) in CREATE VIEW

Prevention

When it happens

Trigger: SELECTing a subset of columns or a reordered list; replacing a column with an expression or cast; the view declaration expecting a row type that no longer matches the source table after the table's schema changed.

Common situations: Hand-written column lists drifting from the table definition; a table altered (column added/retyped) while dependent views stayed unchanged; reordering columns for convenience in the view.

Related errors


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