quickwit-oss/quickwit · error

both lt and lte are set

Error message

both lt and lte are set

What it means

Symmetric to the lower bound check: the upper bound of a range query is built from `lt` (exclusive) and `lte` (inclusive), and supplying both keys is ambiguous. The conversion in range_query.rs bails with this error before constructing the RangeQuery AST.

Source

Thrown at quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs:117

                lte.map(|v| parse_and_convert(v, &parser)).transpose()?,
            )
        } else {
            (gt, gte, lt, lte)
        };

        let range_query_ast = crate::query_ast::RangeQuery {
            field,
            lower_bound: match (gt, gte) {
                (Some(_gt), Some(_gte)) => {
                    anyhow::bail!("both gt and gte are set")
                }
                (Some(gt), None) => Bound::Excluded(gt),
                (None, Some(gte)) => Bound::Included(gte),
                (None, None) => Bound::Unbounded,
            },
            upper_bound: match (lt, lte) {
                (Some(_lt), Some(_lte)) => {
                    anyhow::bail!("both lt and lte are set")
                }
                (Some(lt), None) => Bound::Excluded(lt),
                (None, Some(lte)) => Bound::Included(lte),
                (None, None) => Bound::Unbounded,
            },
        };
        let ast: QueryAst = range_query_ast.into();
        Ok(ast.boost(boost))
    }
}

fn parse_and_convert(literal: JsonLiteral, parser: &StrptimeParser) -> anyhow::Result<JsonLiteral> {
    if let JsonLiteral::String(date_time_str) = literal {
        let parsed_date_time = parser
            .parse_date_time(&date_time_str)
            .map_err(|reason| anyhow::anyhow!("Failed to parse date time: {}", reason))?;
        let parsed_date_time_rfc3339 = parsed_date_time.format(&Rfc3339)?;
        Ok(JsonLiteral::String(parsed_date_time_rfc3339))

View on GitHub (pinned to a39730c5cd)

Solutions

  1. Keep only `lte` for an inclusive upper bound, or only `lt` for exclusive.
  2. In generated queries, serialize only the bound key that is actually set.
  3. Add client-side validation rejecting range objects with both upper-bound keys.

Example fix

// before
{"range": {"timestamp": {"lt": "2024-02-01", "lte": "2024-01-31T23:59:59Z"}}}
// after
{"range": {"timestamp": {"lt": "2024-02-01"}}}
Defensive patterns

Strategy: validation

Validate before calling

if range.contains_key("lt") && range.contains_key("lte") {
    return Err("range upper bound: set only one of lt/lte");
}

Try / catch

match res {
    Err(e) if e.to_string().contains("both lt and lte") => {
        // serialize only one upper bound and retry
    }
    r => r?,
}

Prevention

When it happens

Trigger: Sending a range query JSON containing both `lt` and `lte` keys, e.g. `{"range": {"ts": {"lt": 200, "lte": 100}}}`, through the Elastic range query conversion.

Common situations: Query builders that always serialize both upper-bound keys; users copying Elasticsearch examples that use lt alongside lte; templates parameterizing both exclusive and inclusive endpoints.

Related errors


AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08). Data as JSON: /api/errors/e8cc3496cf9d9a57. Report an issue: GitHub.