quickwit-oss/quickwit · error

concatenate type must have at least one sub-field

Error message

concatenate type must have at least one sub-field

What it means

A `concatenate` field builds a combined searchable text from sub-fields. It must list at least one `concatenate_fields`, unless `include_dynamic_fields` is true (in which case dynamic fields provide the input). An empty list with dynamic fields disabled leaves the concatenate field with no source data, so deserialization bails.

Solutions

  1. Populate `concatenate_fields` with at least one text field name.
  2. Set `include_dynamic_fields: true` if you intend the concatenate field to cover dynamic fields.
  3. If neither applies, remove the concatenate field from the mapping.

Example fix

// before
{name: "all", type: "concatenate", concatenate_fields: [], include_dynamic_fields: false}
// after
{name: "all", type: "concatenate", concatenate_fields: ["title", "body"], include_dynamic_fields: false}
Defensive patterns

Strategy: validation

Validate before calling

fn concatenate_has_sources(f: &serde_json::Value) -> bool {
    if f.get("type").and_then(|t| t.as_str()) == Some("concatenate") {
        let has_fields = f.get("concatenate_fields")
            .and_then(|m| m.as_array())
            .map_or(false, |a| !a.is_empty());
        let dynamic = f.get("include_dynamic_fields")
            .and_then(|v| v.as_bool()) == Some(true);
        has_fields || dynamic
    } else { true }
}

Try / catch

match create_index_result {
    Err(e) if e.to_string().contains("concatenate type must have at least one sub-field") => {
        eprintln!("List concatenate_fields or enable include_dynamic_fields");
    }
    other => other?,
}

Prevention

When it happens

Trigger: A mapping like `{name: "all", type: "concatenate", concatenate_fields: [], include_dynamic_fields: false}` — FieldMappingType::Concatenate deserialization bails.

Common situations: Setting up a 'search everything' concatenate field but forgetting to list fields; auto-generated mappings with empty lists when the intended default was include_dynamic_fields=true; field names removed from the list leaving it empty.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/c2c4f8058d6dbf99. Report an issue: GitHub.

Appendix: source

Thrown at quickwit/quickwit-doc-mapper/src/doc_mapper/field_mapping_entry.rs:740

    quickwit_field_type: QuickwitFieldType,
    json: JsonValue,
) -> anyhow::Result<FieldMappingType> {
    let (typ, cardinality) = match quickwit_field_type {
        QuickwitFieldType::Simple(typ) => (typ, Cardinality::SingleValued),
        QuickwitFieldType::Array(typ) => (typ, Cardinality::MultiValued),
        QuickwitFieldType::Object => {
            let object_options: QuickwitObjectOptions = serde_json::from_value(json)?;
            if object_options.field_mappings.is_empty() {
                anyhow::bail!("object type must have at least one field mapping");
            }
            return Ok(FieldMappingType::Object(object_options));
        }
        QuickwitFieldType::Concatenate => {
            let concatenate_options: QuickwitConcatenateOptions = serde_json::from_value(json)?;
            if concatenate_options.concatenate_fields.is_empty()
                && !concatenate_options.include_dynamic_fields
            {
                anyhow::bail!("concatenate type must have at least one sub-field");
            }
            return Ok(FieldMappingType::Concatenate(concatenate_options));
        }
    };
    match typ {
        Type::Str => {
            let text_options: QuickwitTextOptions = serde_json::from_value(json)?;
            Ok(FieldMappingType::Text(text_options, cardinality))
        }
        Type::U64 => {
            let numeric_options: QuickwitNumericOptions = serde_json::from_value(json)?;
            Ok(FieldMappingType::U64(numeric_options, cardinality))
        }
        Type::I64 => {
            let numeric_options: QuickwitNumericOptions = serde_json::from_value(json)?;
            Ok(FieldMappingType::I64(numeric_options, cardinality))
        }
        Type::F64 => {

View on GitHub (pinned to a39730c5cd)