Commit 3709436
committed
feat(query): add KQL (Kibana Query Language) support
Adds optional KQL parsing as a thin translation layer at the REST entry
point. KQL inputs are parsed and lowered to QueryAst using only existing
variants (BoolQuery, FullTextQuery, RangeQuery, FieldPresenceQuery,
WildcardQuery, MatchAll, UserInputQuery) — the core enum, visitor traits,
tag pruning, and root-search remain unchanged.
Wire surface:
* Native REST: `?kql=<expr>` on /api/v1/{index}/search, mutually
exclusive with the existing `query=` parameter.
* Elastic-compat JSON: `{"query": {"kql": {"query": "...", ...}}}` on
/api/v1/_elastic/{index}/_search. Documented as a Quickwit-only
extension since real Elasticsearch returns parsing_exception.
Supported grammar (matches the public Kibana KQL reference):
field-value, quoted phrases, bare default-field terms, `*` (match-all
fast path), `?`/`*` wildcards, `field:*` exists, `field:>=N` /
`>` / `<=` / `<` ranges (numeric literals coerced to JsonLiteral::Number,
non-numeric falls back to String), boolean and/or/not (case-insensitive),
juxtaposition as implicit AND, parens, `field:(a or b)` value groups
with proper field distribution, escape semantics (`\and`, `\:`, `\+`).
Safety rails (all return HTTP 400 with specific error messages):
* Max KQL input length: 16 KiB (REST layer)
* Max parser nesting depth: 64
* Max bare-token length: 1 KiB
* Max quoted-phrase length: 4 KiB
* `{...}` nested-field syntax rejected (Quickwit has no nested type)
* Nested field qualifier inside value group rejected
* `query` and `kql` mutually exclusive
Observability:
* quickwit_kql_parse_total counter
* quickwit_kql_parse_failures_total counter
* quickwit_kql_parse_duration_seconds histogram
* Structured `kql=<bool>`, `tantivy_grammar=<bool>` fields on search
log lines so SRE can split KQL vs Tantivy-grammar traffic without
parsing raw query strings.
OpenAPI:
* `query` is now `#[serde(default)]` (semantically optional at the
wire layer); utoipa override exposes both `query` and `kql` as
`Option<String>` so generated SDK clients no longer encode the
obsolete `required: ["query"]` contract.
Tests:
* 246 unit tests in quickwit-query covering lexer, parser
(recursive-descent with depth guard), lowering (with
Tantivy-grammar escape handling for default-field deferral),
metrics wiring, JSON DSL deserialization, and proptest fuzz
(~6k cases) confirming the parser never panics on arbitrary input.
* Kibana conformance corpus pinning expected ASTs for each
documented KQL idiom + explicit notes on intentional divergences.
* REST handler unit tests for `kql`/`query` mutual exclusion,
whitespace handling, size caps, and search_fields propagation.
* Integration scenarios under rest-api-tests/scenarii/kql_search/
asserting exact hit counts against a known dataset.
* Concurrent load harness (load_test.py) mixing happy-path and
adversarial shapes; multi-node docker-compose template for
distributed root→leaf testing.
Line coverage on KQL production code: 95-99% per file; the remaining
gaps are defensive code, test panic-guards in let-else patterns, and
lazy_counter!/lazy_histogram! macro internals the coverage tool cannot
introspect.
Files modified outside the new kql/ module: 5 (Cargo.lock,
quickwit-cli/src/tool.rs, quickwit-query/Cargo.toml,
quickwit-query/src/elastic_query_dsl/mod.rs,
quickwit-query/src/lib.rs, quickwit-serve/src/search_api/rest_handler.rs).
The core QueryAst enum, QueryAstVisitor, QueryAstTransformer,
tag_pruning, and root-search are untouched.1 parent 9c5a2f2 commit 3709436
24 files changed
Lines changed: 3635 additions & 7 deletions
File tree
- quickwit
- quickwit-cli/src
- quickwit-query
- src
- kql
- quickwit-serve/src/search_api
- rest-api-tests/scenarii/kql_search
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
542 | 542 | | |
543 | 543 | | |
544 | 544 | | |
| 545 | + | |
545 | 546 | | |
546 | 547 | | |
547 | 548 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
0 commit comments