quickwit-oss/tantivy · error

At least one bound must be set

Error message

At least one bound must be set

What it means

RangeFastFieldWeight::scorer (src/query/range_query/range_query_fastfield.rs:64) unwraps bounds.get_inner() with "At least one bound must be set" to obtain the term carrying the field and type; with no bounds set the panic fires. Like the other range queries, a boundless range is considered an invalid query object and fails fast at scoring time (also reachable via explain()).

Solutions

  1. Set at least one bound before executing or explaining the query.
  2. Check bounds in the builder layer and return a user-facing error instead of building the query object.
  3. For "match everything" semantics use the all-query or the documented unbounded range constructors rather than an empty range.

Example fix

// before
let weight = fast_field_range_query.weight(...)?;
let scorer = weight.scorer(&reader, 1.0)?; // panics if bounds empty

// after
if bounds.lower_bound.is_none() && bounds.upper_bound.is_none() {
    return Ok(Box::new(AllScorer::new(reader.max_doc()))); // or return Err(...)
}
let scorer = weight.scorer(&reader, 1.0)?;
Defensive patterns

Strategy: validation

Validate before calling

if bounds.lower_bound.is_none() && bounds.upper_bound.is_none() {
    // fall back to AllScorer or return an error before scoring
}

Prevention

When it happens

Trigger: Calling weight(...).scorer(...) or explain() on a fast-field range query whose RangeBounds never had lower_bound/upper_bound set; also hit from tests like test_range_regression_simplified when constructing the weight manually.

Common situations: Programmatic construction of fast-field range queries with both bound branches skipped; empty range objects from parsed/deserialized queries; copy-pasted builder code where setter calls were removed.

Related errors


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

Appendix: source

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

impl FastFieldRangeWeight {
    /// Create a new FastFieldRangeWeight
    pub fn new(bounds: BoundsRange<Term>) -> Self {
        Self { bounds }
    }
}

impl Weight for FastFieldRangeWeight {
    fn scorer(&self, reader: &SegmentReader, boost: Score) -> crate::Result<Box<dyn Scorer>> {
        // Check if both bounds are Bound::Unbounded
        if self.bounds.is_unbounded() {
            return Ok(Box::new(AllScorer::new(reader.max_doc())));
        }

        let term = self
            .bounds
            .get_inner()
            .expect("At least one bound must be set");
        let schema = reader.schema();
        let field_type = schema.get_field_entry(term.field()).field_type();
        assert_eq!(
            term.typ(),
            field_type.value_type(),
            "Field is of type {:?}, but got term of type {:?}",
            field_type,
            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());

View on GitHub (pinned to b5d8deb80c)