8000 fix(api): reject avg/sum/min/max/stats on a non-numeric field like ES by amartya1523 · Pull Request #2913 · xerj-org/xerj · GitHub
[go: up one dir, main page]

Skip to content

fix(api): reject avg/sum/min/max/stats on a non-numeric field like ES - #2913

Merged
xerj-org merged 2 commits into
xerj-org:mainfrom
amartya1523:fix/metric-agg-field-type
Nov 4, 2026
Merged

xerj-org merged 2 commits into
xerj-org:mainfrom
amartya1523:fix/metric-agg-field-type

Conversation

@amartya1523
8000 Copy link
Copy Markdown
Contributor

AI-assisted change, run and reviewed by @amartya1523.

What this changes, and why

A numeric metric aggregation on a non-numeric field answered 200 with a null value. ES 8.13 fails every shard with Field [f] of type [t] is not supported for aggregation [avg] (ValuesSourceRegistry#getAggregator) and answers 400. A typo'd or wrong field therefore looked like an empty index, and sum on a keyword even returned -0.0.

Four catch-lenient.txt lines are this case: {avg,max,min,sum}_metric.yml · Aggregating wrong datatype test. #1861 left it out on purpose because the check needs the mapping ("avg on a keyword field ... is left alone").

Fix: find_unsupported_metric_agg_field runs in search_impl after the field-alias rewrite, so an alias is judged by its target.

  • It walks the aggregation tree, sub-aggregations included.
  • For avg / sum / min / max / stats / extended_stats, it resolves field in each participating index's mapping, following object properties and multi-field fields (name.raw).
  • It refuses only types ES never accepts here (keyword, constant_keyword, wildcard, ip, geo_point, version), and only when every target index maps the field that way. An unmapped field, or one that is numeric in at least one target, still answers 200, as ES does (a partial-shard success there).
  • The error uses the existing search_shard_validation_error envelope: search_phase_execution_exception wrapping illegal_argument_exception.

Evidence

Wire, node built from main b78004832 vs this branch (index t: s keyword, ip ip, name.raw keyword multi-field, n integer; index u: s long):

POST /t/_search {"aggs":{"a":{"avg":{"field":"s"}}}}
  main:   200 {"aggregations":{"a":{"value":null}}}
  branch: 400 root_cause illegal_argument_exception "Field [s] of type [keyword] is not supported for aggregation [avg]"
POST /t/_search {"aggs":{"a":{"sum":{"field":"name.raw"}}}}
  main:   200 {"aggregations":{"a":{"value":-0.0}}}
  branch: 400 "Field [name.raw] of type [keyword] is not supported for aggregation [sum]"
POST /t/_search {"aggs":{"a":{"max":{"field":"ip"}}}}                    main 200 -> branch 400
POST /t/_search terms(s) > min(s)                                       main 200 -> branch 400
POST /t,u/_search {"aggs":{"a":{"avg":{"field":"s"}}}}                   200 on both (long in u)
POST /t/_search avg(n), avg(nope)                                       200 on both

New test module metric_agg_field_type_tests (es_compat.rs), run against main first:

# main
test es_compat::metric_agg_field_type_tests::numeric_metric_on_a_non_numeric_field_is_a_400 ... FAILED
  {"a":{"avg":{"field":"s"}}} -> {..."aggregations":{"a":{"value":null}}}
  left: 200  right: 400
test es_compat::metric_agg_field_type_tests::numeric_unmapped_and_mixed_targets_still_answer ... ok

# this branch
test es_compat::metric_agg_field_type_tests::numeric_metric_on_a_non_numeric_field_is_a_400 ... ok
test es_compat::metric_agg_field_type_tests::numeric_unmapped_and_mixed_targets_still_answer ... ok

Checks

  • cargo fmt --all
  • Scoped build of the touched crate, quick profile (cargo build --profile quick -p xerj-server, per docs/FAST_BUILDS.md). Not --release; see Not run.
  • cargo test --profile quick -p xerj-api --lib: 417 passed, 0 failed (all three sibling fixes applied)
  • ES-YAML suite at 0 failed: 1453 passed · 0 failed · 10 skipped · 1463 total, 65 cases catch-lenient (was 74), no stale-entry notes. Run with es-yaml-runner --dir tests/es-compat-yaml/yaml against a quick node built from main b78004832 plus this PR and its two siblings.
  • YAML case: the four existing Aggregating wrong datatype test cases now enforce it; their catch-lenient lines are deleted.
  • Integration tests run (each alone, quick): the 31 xerj-api/tests files that use aggregations or scroll. 30 passed. cluster_metadata_replicates failed a different replica-timing test on each of two runs (a_restarted_replica_… then a_late_member_copies_…, a 503 replica_not_acknowledged timeout). Both failures are write-acknowledgement timeouts, not search responses.
  • Docs: none describe the old behaviour.

Not run: --release builds (disk on this machine is at 99%, and fat LTO would not fit; I used the quick profile that FAST_BUILDS.md documents for verification). cargo clippy locally (CI runs it). The other ~80 xerj-api integration test binaries, for the same disk reason.

Provenance

  • Written by: AI-assisted, run and reviewed by @amartya1523
  • Verified: the commands and outputs above, on macOS 26 (aarch64), quick profile, based on main b78004832.
  • Assumed: stats and extended_stats reject the same types as avg (ES registers all six on NUMERIC/DATE/BOOLEAN). The YAML suite only exercises the four. text is left alone on purpose: ES rejects it with the fielddata error, which this message would misreport. Range and flattened fields are not refused; ES rejects them too, so they stay as they were.

Motivation:
`{"aggs": {"a": {"avg": {"field": "string_field"}}}}` on a keyword field
answered 200 with `"value": null` (`sum` answered `-0.0`), so a metric
pointed at the wrong field looked like an empty index. ES 8.13 fails
every shard with `Field [string_field] of type [keyword] is not
supported for aggregation [avg]` (ValuesSourceRegistry#getAggregator)
and answers 400. Four catch-lenient lines are this case
(`{avg,max,min,sum}_metric.yml · Aggregating wrong datatype test`).

Root cause:
`find_invalid_agg_params` (xerj-org#1861) deliberately made only mapping-free
checks; nothing on the search path compared a metric's `field` with
the mapped type.

Fix:
`find_unsupported_metric_agg_field`, called in `search_impl` after the
field-alias rewrite, walks the aggregation tree (sub-aggregations
included) and resolves each `avg`/`sum`/`min`/`max`/`stats`/
`extended_stats` field through the participating indices' mappings,
following object `properties` and multi-field `fields` (`name.raw`).
It refuses only types ES never accepts for these aggregations (keyword,
constant_keyword, wildcard, ip, geo_point, version), and only when
every target index maps the field that way: an unmapped field, or one
that is numeric in at least one target, still answers 200 as ES does
(a partial-shard success there). The error uses the existing
`search_shard_validation_error` envelope (search_phase_execution_exception
wrapping illegal_argument_exception).

Evidence:
- ES-YAML suite against a node built from this change (with its two
  sibling scroll fixes): `1453 passed · 0 failed · 10 skipped`, no stale
  catch-lenient entries; the four lines above are deleted.
Runs through the ES router: avg on a keyword, max on an ip, sum on a
keyword multi-field, stats through a field alias and min under a terms
bucket each answer 400 with ES's reason; avg on a numeric field, on an
unmapped field, across a keyword/long pair of indices, and value_count
on a keyword still answer 200.

On main the first test fails with the 200 it used to return:
  {"a":{"avg":{"field":"s"}}} -> {..."aggregations":{"a":{"value":null}}}
  left: 200  right: 400
@xerj-org
xerj-org commented Nov 4, 2026
Copy link
Copy Markdown
Owner

Xerj Merger made the fix: rejects avg/sum/min/max/stats/extended_stats on a non-numeric mapped field with ES's 400 error instead of answering 200 with null. Diff reviewed, Build + Test green on fc8a3c6 (cargo-audit was red only from RUSTSEC-2026-0436, fixed on main by #2912). Thanks @amartya1523!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0