quickwit-oss/tantivy · error

At least one bound must be set

Error message

At least one bound must be set

What it means

RangeQuery::get_term (src/query/range_query/range_query.rs:99) unwraps bounds.get_inner(), which is Some only when at least one of lower_bound/upper_bound has been set. A RangeQuery constructed with no bounds at all panics here with "At least one bound must be set". A range without any bound is meaningless, so tantivy treats it as a programmer error rather than a Result.

Source

Thrown at src/query/range_query/range_query.rs:99

        RangeQuery {
            bounds: BoundsRange::new(lower_bound, upper_bound),
        }
    }

    /// Field to search over
    pub fn field(&self) -> Field {
        self.get_term().field()
    }

    /// The value type of the field
    pub fn value_type(&self) -> Type {
        self.get_term().typ()
    }

    pub(crate) fn get_term(&self) -> &Term {
        self.bounds
            .get_inner()
            .expect("At least one bound must be set")
    }
}

impl Query for RangeQuery {
    fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>> {
        let schema = enable_scoring.schema();
        let field_type = schema.get_field_entry(self.field()).field_type();

        if field_type.is_fast() && is_type_valid_for_fastfield_range_query(self.value_type()) {
            Ok(Box::new(FastFieldRangeWeight::new(self.bounds.clone())))
        } else {
            if field_type.is_json() {
                return Err(crate::TantivyError::InvalidArgument(
                    "RangeQuery on JSON is only supported for fast fields currently".to_string(),
                ));
            }
            Ok(Box::new(InvertedIndexRangeWeight::new(
                self.field(),

View on GitHub (pinned to b5d8deb80c)

Solutions

  1. Set at least one bound before executing: call lower_bound or upper_bound (or the greater_than/less_than builder methods).
  2. For open-ended ranges, use the documented unbounded helpers or a MatchAll query instead of an empty range.
  3. Validate deserialized query JSON: reject range objects whose bounds map is empty before converting to RangeQuery.
  4. If bounds are dynamic, fall back to a different query type when no bound is provided.

Example fix

// before
let query = RangeQuery::new_term_range(field, bounds); // bounds has neither lower nor upper
let top = searcher.search(&query, &collector)?; // panics

// after
if bounds.lower_bound.is_none() && bounds.upper_bound.is_none() {
    return Err(TantivyError::InvalidArgument("range query needs at least one bound".into()));
}
let top = searcher.search(&query, &collector)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_any_bound(b: &RangeBounds) -> bool {
    b.lower_bound.is_some() || b.upper_bound.is_some()
}
if !has_any_bound(&bounds) { return Err(...); }

Prevention

When it happens

Trigger: Building a RangeQuery via RangeQueryBuilder (or the struct directly) and calling weight()/field()/value_type()/get_term() without ever calling the lower_bound or upper_bound setters (greater_than/less_than family). Executing such a query via searcher.search() triggers weight() and thus the panic.

Common situations: Programmatic query builders where both bound-setting calls are behind conditional branches that both evaluated false; deserializing query JSON like {"range":{"price":{}}} with an empty range object into a RangeQuery; refactors that renamed bound setters so they silently stopped being called.

Related errors


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