quickwit-oss/quickwit · error
both gt and gte are set
Error message
both gt and gte are set
What it means
When converting an Elastic range query into the internal RangeQuery AST, the lower bound is built from `gt` (exclusive) and `gte` (inclusive). Quickwit requires exactly one or neither; supplying both is ambiguous because the two bounds imply different inclusivity, so the conversion bails with this error.
Source
Thrown at quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs:109
let (gt, gte, lt, lte) = if let Some(JsonLiteral::String(java_date_format)) = format {
let parser = StrptimeParser::from_java_datetime_format(&java_date_format)
.map_err(|err| anyhow::anyhow!("failed to parse range query date format. {err}"))?;
(
gt.map(|v| parse_and_convert(v, &parser)).transpose()?,
gte.map(|v| parse_and_convert(v, &parser)).transpose()?,
lt.map(|v| parse_and_convert(v, &parser)).transpose()?,
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))
}
}View on GitHub (pinned to a39730c5cd)
Solutions
- Remove one of the two keys: keep `gte` for an inclusive lower bound, `gt` for exclusive.
- If bounds come from variables, emit only the bound you actually have (skip the other key entirely).
- Validate the range request object before submitting to reject duplicated bounds early.
Example fix
// before
{"range": {"timestamp": {"gt": "2024-01-01", "gte": "2024-01-01T00:00:00Z"}}}
// after
{"range": {"timestamp": {"gte": "2024-01-01T00:00:00Z"}}} Defensive patterns
Strategy: validation
Validate before calling
let keys: Vec<&str> = range.keys().map(|k| k.as_str()).collect();
if keys.contains(&"gt") && keys.contains(&"gte") {
return Err("range lower bound: set only one of gt/gte");
} Try / catch
match res {
Err(e) if e.to_string().contains("both gt and gte") => {
// drop one bound and retry
}
r => r?,
} Prevention
- Build range bounds from Option values and serialize only the Some variant.
- Normalize user input: convert gt+gte conflicts to a single bound with a documented rule.
- Add unit tests for your query builder covering mixed bound keys.
When it happens
Trigger: Sending a range query JSON containing both `gt` and `gte` keys, e.g. `{"range": {"ts": {"gt": 100, "gte": 50}}}`, through convert_to_query_ast for RangeQuery.
Common situations: Programmatic query builders that unconditionally serialize both bound keys (one null/default); hand-written JSON where the user copied examples mixing gt and gte; template code that fills both boundaries.
Related errors
- both lt and lte 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/cf12ec0c436a25aa.
Report an issue: GitHub.