{"record":{"id":"cf12ec0c436a25aa","repo":"quickwit-oss/quickwit","slug":"both-gt-and-gte-are-set","errorCode":null,"errorMessage":"both gt and gte are set","messagePattern":"both gt and gte are set","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs","lineNumber":109,"sourceCode":"\n        let (gt, gte, lt, lte) = if let Some(JsonLiteral::String(java_date_format)) = format {\n            let parser = StrptimeParser::from_java_datetime_format(&java_date_format)\n                .map_err(|err| anyhow::anyhow!(\"failed to parse range query date format. {err}\"))?;\n            (\n                gt.map(|v| parse_and_convert(v, &parser)).transpose()?,\n                gte.map(|v| parse_and_convert(v, &parser)).transpose()?,\n                lt.map(|v| parse_and_convert(v, &parser)).transpose()?,\n                lte.map(|v| parse_and_convert(v, &parser)).transpose()?,\n            )\n        } else {\n            (gt, gte, lt, lte)\n        };\n\n        let range_query_ast = crate::query_ast::RangeQuery {\n            field,\n            lower_bound: match (gt, gte) {\n                (Some(_gt), Some(_gte)) => {\n                    anyhow::bail!(\"both gt and gte are set\")\n                }\n                (Some(gt), None) => Bound::Excluded(gt),\n                (None, Some(gte)) => Bound::Included(gte),\n                (None, None) => Bound::Unbounded,\n            },\n            upper_bound: match (lt, lte) {\n                (Some(_lt), Some(_lte)) => {\n                    anyhow::bail!(\"both lt and lte are set\")\n                }\n                (Some(lt), None) => Bound::Excluded(lt),\n                (None, Some(lte)) => Bound::Included(lte),\n                (None, None) => Bound::Unbounded,\n            },\n        };\n        let ast: QueryAst = range_query_ast.into();\n        Ok(ast.boost(boost))\n    }\n}","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/quickwit-oss/quickwit/blob/a39730c5cdcd1a4fe798403737ae293999ea21f8/quickwit/quickwit-query/src/elastic_query_dsl/range_query.rs#L91-L127","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n{\"range\": {\"timestamp\": {\"gt\": \"2024-01-01\", \"gte\": \"2024-01-01T00:00:00Z\"}}}\n// after\n{\"range\": {\"timestamp\": {\"gte\": \"2024-01-01T00:00:00Z\"}}}","handlingStrategy":"validation","validationCode":"let keys: Vec<&str> = range.keys().map(|k| k.as_str()).collect();\nif keys.contains(&\"gt\") && keys.contains(&\"gte\") {\n    return Err(\"range lower bound: set only one of gt/gte\");\n}","typeGuard":null,"tryCatchPattern":"match res {\n    Err(e) if e.to_string().contains(\"both gt and gte\") => {\n        // drop one bound and retry\n    }\n    r => r?,\n}","preventionTips":["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."],"tags":["range-query","query-dsl","validation"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"a39730c5cdcd1a4fe798403737ae293999ea21f8","analyzedAt":"2026-09-08T13:19:37.784Z","contentChangedAt":"2026-09-08T13:19:37.784Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}