{"record":{"id":"9bfc43b8dde55f0f","repo":"quickwit-oss/tantivy","slug":"expected-json-type-in-term","errorCode":null,"errorMessage":"expected json type in term","messagePattern":"expected json type in term","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/query/range_query/range_query_fastfield.rs","lineNumber":90,"sourceCode":"            term.typ()\n        );\n        let field_name = term.get_full_path(reader.schema());\n\n        let get_value_bytes = |term: &Term| term.value().value_bytes_payload();\n\n        let term_value = term.value();\n        if field_type.is_json() {\n            let bounds = self\n                .bounds\n                .map_bound(|term| term.value().as_json_value_bytes().unwrap().to_owned());\n            // Unlike with other field types JSON may have multiple columns of different types\n            // under the same name\n            //\n            // In the JSON case the provided type in term may not exactly match the column type,\n            // especially with the numeric type interpolation\n            let json_value_bytes = term_value\n                .as_json_value_bytes()\n                .expect(\"expected json type in term\");\n            let typ = json_value_bytes.typ();\n\n            match typ {\n                Type::Str => {\n                    let Some(str_dict_column): Option<StrColumn> =\n                        reader.fast_fields().str(&field_name)?\n                    else {\n                        return Ok(Box::new(EmptyScorer));\n                    };\n                    let dict = str_dict_column.dictionary();\n\n                    let bounds = self.bounds.map_bound(get_value_bytes);\n                    // Get term ids for terms\n                    let (lower_bound, upper_bound) =\n                        dict.term_bounds_to_ord(bounds.lower_bound, bounds.upper_bound)?;\n                    let fast_field_reader = reader.fast_fields();\n                    let Some((column, _col_type)) = fast_field_reader\n                        .u64_lenient_for_type(Some(&[ColumnType::Str]), &field_name)?","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/quickwit-oss/tantivy/blob/b5d8deb80c26924e6b007a5b1a7630f35ca64de4/src/query/range_query/range_query_fastfield.rs#L72-L108","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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).","If bounds come from user input, convert them with the documented range bound APIs (date/u64/i64/f64 helpers) instead of manual Term bytes.","Rebuild queries after schema migrations; do not reuse Term objects built for the old field type."],"exampleFix":"// before\nlet term = Term::from_field_u64(field, 42); // non-JSON bytes\nlet query = make_fast_field_range(field, term); // panics in scorer\n\n// after\nlet mut term = Term::with_type_and_field(Type::Json, field);\nterm.append_type_and_fast_value(42u64); // JSON-encoded value bytes\nlet query = make_fast_field_range(field, term);","handlingStrategy":"validation","validationCode":"fn is_json_term(t: &Term) -> bool { t.typ() == Type::Json }\nif !is_json_term(&bound_term) { return Err(TantivyError::InvalidArgument(\"bound must be a JSON term\".into())); }","typeGuard":"fn as_json_term(t: &Term) -> Option<&Term> {\n    (t.typ() == Type::Json).then_some(t)\n}","tryCatchPattern":null,"preventionTips":["Build bound terms with JSON-aware constructors (append_type_and_fast_value on a Type::Json term)","Confirm the field is a JSON fast field before using fast-field range queries","Don't reuse primitive-typed Terms after schema migrations","Rely on the documented range bound APIs instead of raw Term bytes"],"tags":["rust","panic","query","range-query","json","fast-fields","type-mismatch"],"backgroundTag":"term-type-mismatch","analyzedSha":"b5d8deb80c26924e6b007a5b1a7630f35ca64de4","analyzedAt":"2026-09-05T13:20:51.521Z","contentChangedAt":"2026-09-05T13:20:51.521Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}