quickwit-oss/tantivy · error

expected json type in term

Error message

expected json type in term

What it means

RangeFastFieldWeight::scorer converts the range-bound term into JSON value bytes via term_value.as_json_value_bytes().expect("expected json type in term") (src/query/range_query/range_query_fastfield.rs:90). This expects the bound term to carry JSON-encoded value bytes; a term whose type is not JSON-encoded makes the conversion None and panics. It occurs when a fast-field range query is built with bound terms created by a non-JSON path, so the term type disagrees with the JSON column handling.

Source

Thrown at src/query/range_query/range_query_fastfield.rs:90

            term.typ()
        );
        let field_name = term.get_full_path(reader.schema());

        let get_value_bytes = |term: &Term| term.value().value_bytes_payload();

        let term_value = term.value();
        if field_type.is_json() {
            let bounds = self
                .bounds
                .map_bound(|term| term.value().as_json_value_bytes().unwrap().to_owned());
            // Unlike with other field types JSON may have multiple columns of different types
            // under the same name
            //
            // In the JSON case the provided type in term may not exactly match the column type,
            // especially with the numeric type interpolation
            let json_value_bytes = term_value
                .as_json_value_bytes()
                .expect("expected json type in term");
            let typ = json_value_bytes.typ();

            match typ {
                Type::Str => {
                    let Some(str_dict_column): Option<StrColumn> =
                        reader.fast_fields().str(&field_name)?
                    else {
                        return Ok(Box::new(EmptyScorer));
                    };
                    let dict = str_dict_column.dictionary();

                    let bounds = self.bounds.map_bound(get_value_bytes);
                    // Get term ids for terms
                    let (lower_bound, upper_bound) =
                        dict.term_bounds_to_ord(bounds.lower_bound, bounds.upper_bound)?;
                    let fast_field_reader = reader.fast_fields();
                    let Some((column, _col_type)) = fast_field_reader
                        .u64_lenient_for_type(Some(&[ColumnType::Str]), &field_name)?

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Create the bound terms with the JSON-aware constructors (a Type::Json term with append_type_and_fast_value) so the value bytes carry a JSON type prefix.
  2. Verify the schema: ensure the queried field is actually the JSON/fast-field type you assume, and that term.typ() matches field_type.value_type() (the assert above this line checks it).
  3. If bounds come from user input, convert them with the documented range bound APIs (date/u64/i64/f64 helpers) instead of manual Term bytes.
  4. Rebuild queries after schema migrations; do not reuse Term objects built for the old field type.

Example fix

// before
let term = Term::from_field_u64(field, 42); // non-JSON bytes
let query = make_fast_field_range(field, term); // panics in scorer

// after
let mut term = Term::with_type_and_field(Type::Json, field);
term.append_type_and_fast_value(42u64); // JSON-encoded value bytes
let query = make_fast_field_range(field, term);
Defensive patterns

Strategy: validation

Validate before calling

fn is_json_term(t: &Term) -> bool { t.typ() == Type::Json }
if !is_json_term(&bound_term) { return Err(TantivyError::InvalidArgument("bound must be a JSON term".into())); }

Type guard

fn as_json_term(t: &Term) -> Option<&Term> {
    (t.typ() == Type::Json).then_some(t)
}

Prevention

When it happens

Trigger: Building a fast-field range query where the bound Term was constructed for a non-JSON field type (e.g. Term::from_field_u64 on a field handled as a JSON fast field), so as_json_value_bytes returns None at scorer()/explain() time.

Common situations: Mismatch between the query's declared field type and the term factory used (schema evolved from primitive to JSON fast field); hand-built Terms in custom query code; version changes where fast-field range queries started requiring JSON-encoded bound terms.

Related errors


AI-assisted analysis of quickwit-oss/tantivy@b5d8deb80c (2026-09-05). Data as JSON: /api/errors/9bfc43b8dde55f0f. Report an issue: GitHub.