PRQL/prql · error · Error

expected a table, found

Error message

expected a table, found {found_desc}

What it means

A pipeline stage that requires a table (e.g. `from`) was given an expression that is not a table. When the found expression has no relation id — for instance a bare lambda like `x -> y` or a plain function — the resolver reports what kind of thing it actually found instead of 'a table'.

Solutions

  1. Call the function if it returns a table: `from (my_func arg)` rather than `from my_func`.
  2. Replace the lambda/function with an actual table reference, e.g. `from employees`.
  3. Check the expression's type: it must be a relation/table to sit in a `from` position.

Example fix

// before
from (x -> x)
// after
from employees
Defensive patterns

Strategy: type-guard

Validate before calling

// ensure the argument is a table reference before putting it in `from`
if (typeof source !== 'string' && !isTableCall(source)) throw new Error('from requires a table');

Type guard

const isTableRef = (e) => typeof e === 'string' || (e.kind === 'ident' && !e.isLambda && !e.isFunc);

Try / catch

try { compile(prql) } catch (e) { if (e.message.includes('expected a table')) { /* replace the expression with a table or call the function */ } }

Prevention

When it happens

Trigger: Writing `from (x -> y)` (bare lambda), `from some_function`, or piping a function/literal into a transform that expects a relation rather than a table-typed expression.

Common situations: Confusing functions/lambdas with relations, forgetting to call a function that returns a table, or passing a scalar expression where a table is required.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of PRQL/prql@e164e249b9 (2026-09-09). Data as JSON: /api/errors/f706843fecf09f83. Report an issue: GitHub.

Appendix: source

Thrown at prqlc/prqlc/src/semantic/resolver/types.rs:111

            return Ok(());
        };

        let Some(found_ty) = &mut found.ty else {
            // found is none: infer from expected

            if found.lineage.is_none() && expected.unwrap().is_relation() {
                // special case: infer a table type
                // inferred tables are needed for s-strings that represent tables
                // similarly as normal table references, we want to be able to infer columns
                // of this table, which means it needs to be defined somewhere
                // in the module structure.
                let Some(id) = found.id else {
                    // Expression has no id - this happens with bare lambdas like `x -> y`
                    let found_desc = match &found.kind {
                        ExprKind::Func(_) => "a function".to_string(),
                        kind => format!("{kind:?}"),
                    };
                    return Err(Error::new(Reason::Expected {
                        who: None,
                        expected: "a table".to_string(),
                        found: found_desc,
                    })
                    .with_span(found.span));
                };

                let frame = self.declare_table_for_literal(id, None, found.alias.clone());

                // override the empty frame with frame of the new table
                found.lineage = Some(frame)
            }

            // base case: infer expected type
            found.ty = expected.cloned();

            return Ok(());
        };

View on GitHub (pinned to e164e249b9)