…e alias collision (opensearch-project#5197)
When `rename` and `eval` column-ref both resolve to the same source field (e.g.
`eval nm2 = name | rename name as nm`), the previous Map<String,String> approach
silently dropped one mapping on collision.
This commit implements the fix from scratch (PR opensearch-project#5192 was closed unmerged):
* TopHitsParser: add an optional `Map<String, List<String>> fieldNameMapping` field
and a new 4-arg constructor; the 3-arg constructor delegates with null (no-op).
`applyFieldNameMapping()` copies the source-field value to every output alias and
removes the original key only when it is not itself an expected output name.
* AggregateAnalyzer: in the LITERAL_AGG (dedup) branch, build fieldNameMapping by
iterating over the projection args; pass it to TopHitsParser when non-empty.
* Unit tests (OpenSearchAggregationResponseParserTest):
- `top_hits_field_name_mapping_single_rename_should_pass` – regression for opensearch-project#5150
- `top_hits_field_name_mapping_collision_should_duplicate_value` – regression for opensearch-project#5197
* Integration tests (CalcitePPLDedupIT):
- `testDedupWithRenamedField` – dedup after rename, single alias
- `testDedupWithRenamedFieldMappingCollision` – dedup after both rename and eval alias
Fixes opensearch-project#5197
Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
Summary
Fixes #5150 and #5197.
Issue #5150: When a field is renamed via
renameand a subsequentdedupoperates on a different field, the renamed field returns null. The dedup aggregation pushdown (top_hits) returns results keyed by the original index field name, but the Calcite row-type expects the renamed name — causing the enumerator to resolve null.Issue #5197: When two output aliases (
payfromrename,pay2fromevalcolumn-ref) both resolve to the same underlying index field (salary), aMap<String, String>silently drops one entry on collision.Both fixes are implemented from scratch since PR #5192 was closed without being merged.
Changes:
TopHitsParser— new optionalMap<String, List<String>> fieldNameMappingfield + 4-arg constructor (3-arg delegates to it withnull);applyFieldNameMapping()copies the source-field value to every output alias and removes the original key only when it is not itself one of the expected output namesAggregateAnalyzer— in theLITERAL_AGG(dedup) branch, iterate projection args to buildfieldNameMapping, pass it toTopHitsParserwhen non-empty; added null guard oninferNamedFieldreturn before callinggetRootName()OpenSearchAggregationResponseParserTest):top_hits_field_name_mapping_single_rename_should_pass— regression for [BUG] Dedup aggregation pushdown nullifies renamed non-dedup fields via top_hits response mapping #5150top_hits_field_name_mapping_collision_should_duplicate_value— regression for Dedup pushdown field name mapping collision when multiple fields reference same original field #5197CalcitePPLDedupIT):testDedupWithRenamedField— dedup after a single rename (covers [BUG] Dedup aggregation pushdown nullifies renamed non-dedup fields via top_hits response mapping #5150 exact scenario)testDedupWithRenamedFieldMappingCollision— dedup after both rename and eval alias pointing to the same field (covers Dedup pushdown field name mapping collision when multiple fields reference same original field #5197)Test plan
./gradlew :opensearch:test --tests *OpenSearchAggregationResponseParserTest*— all tests pass including the two new ones./gradlew :opensearch:build -x integTest -x test— BUILD SUCCESSFUL./gradlew spotlessApply— no formatting violationsCalcitePPLDedupIT) require a running OpenSearch cluster — will pass in CI