fix(engine): terms aggregation ignores script, always returns empty buckets - #50
Merged
Merged
Conversation
… buckets
run_terms required a top-level `field` param and bailed out to
`{"buckets": []}` immediately whenever it was absent -- it never
looked at `script`, so any script-based terms aggregation (e.g.
Kibana's "Heat Map" visualization bucketing by an hour-of-day
Painless script, a common Y-axis dimension) silently returned zero
buckets regardless of how many documents matched the query.
The rest of run_terms (ordering, min_doc_count, include/exclude,
partitioning, sub-agg computation, sum_other_doc_count) is agnostic
to how a bucket's string key was derived, so this only needed a
key-source abstraction (FieldOrScript) slotted into the existing
field-based extract_field_values() call sites. Script evaluation
reuses crate::painless::eval_painless/PainlessCtx, the same engine
already proven correct for script_fields and top_hits scripts.
Note: this fixes the aggregation-dispatch gap specifically. It does
not add any new Painless language features -- a script's own
semantics (e.g. date-value methods) are unrelated to this fix and
out of scope here.
Verified: `{"terms": {"script": {"source": "return 5;"}}}` now
produces real buckets keyed on the script's per-doc result instead
of an unconditional empty array. Full ES-compat YAML conformance
suite: 1360 passed, 0 failed, 3 skipped -- no regressions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016XJaZygeuRfZfUg2B8tPKU
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
termsaggregation required a top-levelfieldparam and bailed out to{"buckets": []}immediately when it was absent — it never checked forscript, so any script-based terms aggregation silently returned zero buckets regardless of how many documents matched the query. This breaks any Kibana visualization that buckets by a Painless script rather than a plain field — a common pattern (e.g. a "Heat Map" viz bucketing by an hour-of-day script for its Y-axis).Root cause
This is the very first thing
run_termsdoes — it bails before touchingdocsat all iffieldis missing, with no branch anywhere in the function that looks atparams.get("script").The rest of
run_terms(ordering,min_doc_count,include/exclude, partitioning, sub-agg computation,sum_other_doc_count) is agnostic to how a bucket's string key was derived — it just needsVec<String>per doc — so this only required a key-source abstraction slotted into the field-based call sites, reusing the Painless evaluation engine (crate::painless::eval_painless/PainlessCtx) already proven correct elsewhere in this codebase forscript_fields(plain search) andtop_hits'runtime_mappings.Fix
Added a small
FieldOrScriptenum andterms_agg_bucket_keys()helper:fieldpresent → unchanged behavior (extract_field_values)fieldabsent,scriptpresent → evaluate the Painless script per-doc via the existing engine, flatten the result the same way a field value would be flattened{"buckets": []}fallback (correct ES behavior — a terms agg needs one or the other)All four call sites inside
run_termsthat previously hardcodedextract_field_values(doc, field)(main counting loop,min_doc_count: 0background-fill loop, and both bucket-doc-membership closures used for sub-agg computation and final bucket assembly) now go through this abstraction.Scope note
This fixes the aggregation-dispatch gap specifically —
termswas silently ignoringscriptentirely. It does not add any new Painless language features; a script's own semantics (e.g. date-value accessor methods) are a separate concern and out of scope here. The same "silently requiresfield, ignoresscript" pattern exists in most of the other bucket/metric aggregations in this file (avg,sum,min,max,stats,cardinality,histogram,date_histogram,percentiles) —termswas the one hit in practice and is fixed here; the rest are a known follow-up.Test plan
cargo build --release -p xerj-api -p xerj-engine -p xerj-servercargo fmt --check/cargo clippy --no-deps -- -D warnings— clean{"terms": {"script": {"source": "return 5;"}}}now produces a real bucket (key: 5.0, doc_count: N) instead of an unconditional empty array🤖 Generated with Claude Code