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()).

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)

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.