dbt-labs/dbt-core · error · IndexError
no expression for '{col}'
Error message
no expression for '{col}' What it means
When generating SQL for the dbt.dag_nodes view (a UNION ALL over the nodes table plus DAG side tables), each requested output column must be mapped to a SQL expression. The expr closure only knows how to render 'resource_type' (as a literal for side tables), and 'unique_id', 'resource_type', 'ingested_at' (as node-table columns). Any other column declared in the view's cols has no expression, so view construction fails loudly instead of silently dropping a column from the information schema.
Source
Thrown at crates/dbt-index-core/src/info_schema/parse_safe.rs:711
/// `dbt.dag_nodes`: the node set unioned with the tables holding the DAG-participating
/// resource types that are not in it.
///
/// Mirrors `info_schema::build_dag_nodes` rather than inventing a rule: keep the node
/// rows whose `resource_type` is a DAG type, keep every row of the side tables (their
/// type is one by definition), and treat a missing `enabled` as enabled, matching the
/// config default. The side tables' `enabled` is in the index even though the
/// information schema does not publish it — a view reads the table, not the artifact.
fn dag_nodes_sql(&self) -> Result<String, IndexError> {
// Written per output column so a column added to the information schema's
// `dag_nodes` fails here instead of silently not appearing.
let expr = |col: &str, resource_type: Option<&str>| -> Result<String, IndexError> {
Ok(match (col, resource_type) {
("resource_type", Some(literal)) => format!("'{literal}'"),
("unique_id" | "resource_type" | "ingested_at", _) => {
format!("{T}.{}", quote(col))
}
_ => return Err(self.err(format!("no expression for '{col}'"))),
})
};
let select = |table: &str, resource_type: Option<&str>| -> Result<String, IndexError> {
let cols = self
.cols
.iter()
.map(|col| Ok(format!("{} AS {}", expr(col, resource_type)?, quote(col))))
.collect::<Result<Vec<_>, IndexError>>()?
.join(", ");
Ok(format!(
"SELECT {cols} FROM {BASE_SCHEMA}.{} AS {T} WHERE COALESCE({T}.\"enabled\", TRUE)",
quote(table),
))
};
let types = DAG_RESOURCE_TYPES
.iter()
.map(|t| format!("'{t}'"))View on GitHub (pinned to 0267ce9170)
Solutions
- Extend the match arm in the expr closure (parse_safe.rs:706) to produce an expression for the new column, e.g. ("new_col", _) => format!("{T}.{}", quote("new_col")).
- If the column only exists on side tables as a literal, add a ("new_col", Some(literal)) arm that emits the constant.
- If the column should not be in dag_nodes, remove it from that view's cols entry in VIEWS.
Example fix
// before
("unique_id" | "resource_type" | "ingested_at", _) => {
format!("{T}.{}", quote(col))
}
_ => return Err(self.err(format!("no expression for '{col}'"))),
// after
("unique_id" | "resource_type" | "ingested_at" | "new_col", _) => {
format!("{T}.{}", quote(col))
}
_ => return Err(self.err(format!("no expression for '{col}'"))), Defensive patterns
Strategy: validation
Validate before calling
// Check every dag_nodes column is renderable before generating SQL
const DAG_NODES_EXPR_COLS: &[&str] = &["unique_id", "resource_type", "ingested_at"];
fn dag_nodes_cols_covered(cols: &[&str]) -> Vec<String> {
cols.iter()
.filter(|c| !DAG_NODES_EXPR_COLS.contains(c) && **c != "resource_type")
.map(|c| format!("no expression for '{c}'"))
.collect()
} Try / catch
match view.create_view_sql() {
Ok(sql) => execute(sql),
Err(IndexError::Other(msg)) if msg.contains("no expression for") => {
eprintln!("view spec error (developer fix needed): {msg}");
}
Err(e) => return Err(e),
} Prevention
- Treat the expr closure's match arms as the contract for dag_nodes columns; update it in the same PR as any VIEWS cols change.
- Add a unit test asserting every column in the dag_nodes view entry maps to an expression.
- Derive cols from a shared constant also referenced by the expr builder to keep them in sync.
When it happens
Trigger: Adding a new column to the dag_nodes information-schema view's `cols` list (VIEWS entry) without extending the match in dag_nodes_sql's expr closure to render that column, then calling create_view_sql() on that view.
Common situations: A contributor extends the information schema's dag_nodes output (e.g. adding 'enabled' or a new metadata field) and updates VIEWS but not the per-column expression builder; the module's own tests then fail with this error.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- must read an index table
- neither '{left}' nor '{right}' has a column '{src}'
- inconsistent park_timeout state: {n}
- inconsistent state in unpark
- failed to generate unique thread ID: bitspace exhausted
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/c59e3b98b08ee342.
Report an issue: GitHub.