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

  1. 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")).
  2. If the column only exists on side tables as a literal, add a ("new_col", Some(literal)) arm that emits the constant.
  3. 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

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


AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/c59e3b98b08ee342. Report an issue: GitHub.