dbt-labs/dbt-core · error

time_partitioning_field must be a string

Error message

time_partitioning_field must be a string

What it means

`PartitionConfig::render` panics when time-ingestion partitioning is active and `time_partitioning_field()` returns a value that is not a string (`expect("time_partitioning_field must be a string")`). The rendered column expression (`alias.field`) requires the time-partitioning field to be a plain string; anything else is treated as a corrupted partition config.

Source

Thrown at crates/dbt-schemas/src/schemas/manifest/bigquery_partition.rs:240

            Self::PARTITION_TIME
        };
        Ok(MinijinjaValue::from(field))
    }

    /// Return the insertable time partitioning field name based on the data type.
    /// Practically, only _PARTITIONTIME works so far.
    pub fn insertable_time_partitioning_field(&self) -> Result<MinijinjaValue, MinijinjaError> {
        Ok(MinijinjaValue::from(Self::PARTITION_TIME))
    }

    /// Render the partition expression
    pub fn render(&self, alias: Option<String>) -> Result<MinijinjaValue, MinijinjaError> {
        let column = if !self.time_ingestion_partitioning() {
            self.field.to_owned()
        } else {
            self.time_partitioning_field()?
                .as_str()
                .expect("time_partitioning_field must be a string")
                .to_owned()
        };

        let column = if let Some(alias) = &alias {
            format!("{alias}.{column}")
        } else {
            column
        };

        let result = if self.data_type_should_be_truncated() {
            format!(
                "{}_trunc({}, {})",
                self.data_type,
                column,
                self.granularity()?
            )
        } else {
            column

View on GitHub (pinned to 0267ce9170)

Solutions

  1. Set `time_ingestion_partitioning` (or `time_partitioning_field`) in the model config as a plain quoted string column name
  2. Validate the partition config shape before rendering (field must be a string when time-ingestion partitioning is enabled)
  3. If you construct `PartitionConfig` programmatically, coerce the field with `.to_string()` before calling `render`

Example fix

// before
self.time_partitioning_field()?.as_str().expect("time_partitioning_field must be a string").to_owned()
// after
match self.time_partitioning_field()?.as_str() {
    Some(s) => s.to_owned(),
    None => return Err(MinijinjaError::new(...)),
}
Defensive patterns

Strategy: validation

Validate before calling

// before rendering
if cfg.time_ingestion_partitioning() {
    let f = cfg.time_partitioning_field().ok_or("missing field")?;
    if f.as_str().is_none() {
        return Err("time_partitioning_field must be a string".into());
    }
}

Type guard

fn has_string_time_field(cfg: &PartitionConfig) -> bool {
    !cfg.time_ingestion_partitioning()
        || cfg.time_partitioning_field().and_then(|v| v.as_str()).is_some()
}

Try / catch

match std::panic::catch_unwind(|| cfg.render(Some(alias))) {
    Ok(v) => v,
    Err(_) => MinijinjaValue::from(""), // or propagate a proper error
}

Prevention

When it happens

Trigger: Calling `render`/`render_`/`render_wrapped` on a `PartitionConfig` where `time_ingestion_partitioning()` is true but `time_partitioning_field` yields a non-string Minijinja value — e.g. `time_ingestion_partitioning` config set with a structured/None field value instead of a column name string.

Common situations: Model config like `time_ingestion_partitioning:` with a misconfigured or empty field; configs produced by another tool or older dbt version storing the field as a non-string; YAML that deserialized the field as a number or mapping.

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 dbt-labs/dbt-core@0267ce9170 (2026-09-07). Data as JSON: /api/errors/8e1848350c0e0c7d. Report an issue: GitHub.