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
- Keep only `lte` for an inclusive upper bound, or only `lt` for exclusive.
- In generated queries, serialize only the bound key that is actually set.
- 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
- Serialize upper bounds conditionally (exactly one key).
- Document inclusive/exclusive semantics so users choose lt vs lte deliberately.
- Validate range objects client-side before hitting the API.
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
- both gt and gte are set
- fields and default_field cannot be both set in `query_string
- not yet implemented
- failed to parse host: `{host}`
- failed to parse address `{}`: hostname is invalid
AI-assisted analysis of quickwit-oss/quickwit@a39730c5cd (2026-09-08).
Data as JSON: /api/errors/e8cc3496cf9d9a57.
Report an issue: GitHub.