PRQL/prql · error · Error

expected an identifier, found

Error message

expected an identifier, found {found}

What it means

Lowering converts resolved semantic expressions into SQL IR, and some constructs (like `std.all` expansion results) can only be lowered when they end up as plain identifiers. If `find_selected_all` or the expr lowering match reaches any expression kind that is not an identifier where one is required, this error is thrown.

Solutions

  1. Replace the offending expression with a plain column identifier
  2. Expand the desired columns explicitly instead of relying on `all`/`except` for expression entries
  3. Simplify the `except` list to contain only column names

Example fix

// before
select { all(), except { count() } }
// after
select { all(), except { value } }  # except only column names
Defensive patterns

Strategy: validation

Validate before calling

// Restrict all/except to plain column identifiers
const isIdent = (s) => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(s);
if (!except.every(isIdent)) throw new Error("except must list plain column names");

Type guard

function isPlainIdentifier(node) {
  return node && node.kind === "ident";
}

Try / catch

try {
  compile(query);
} catch (e) {
  if (e.message.includes("expected an identifier")) {
    console.error("all/except selections must expand to identifiers; expand columns manually.");
  } else { throw e; }
}

Prevention

When it happens

Trigger: An `all` selection expansion (within/except) produces a non-identifier node that reaches the lowering path expecting a column name, e.g. complex expressions combined with `except` that do not reduce to identifiers.

Common situations: Using `all` / `except` with expressions like function calls or literals inside the selection, which cannot be named as columns in the output.

Related errors


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

Appendix: source

Thrown at prqlc/prqlc/src/semantic/lowering.rs:793

                continue;
            }

            let id = e.target_id.unwrap();
            match e.kind {
                pl::ExprKind::Ident(_) if e.ty.as_ref().is_some_and(|x| x.kind.is_tuple()) => {
                    res.extend(self.find_selected_all(e, None).with_span(except.span)?);
                }
                pl::ExprKind::Ident(ident) => {
                    res.insert(
                        self.lookup_cid(id, Some(&ident.name))
                            .with_span(except.span)?,
                    );
                }
                pl::ExprKind::All { within, except } => {
                    res.extend(self.find_selected_all(*within, Some(*except))?)
                }
                _ => {
                    return Err(Error::new(Reason::Expected {
                        who: None,
                        expected: "an identifier".to_string(),
                        found: write_pl(e),
                    }));
                }
            }
        }
        Ok(res)
    }

    fn declare_as_column(
        &mut self,
        mut expr_ast: pl::Expr,
        is_aggregation: bool,
    ) -> Result<rq::CId> {
        // short-circuit if this node has already been lowered
        if let Some(LoweredTarget::Compute(lowered)) = self.node_mapping.get(&expr_ast.id.unwrap())
        {

View on GitHub (pinned to e164e249b9)