fix(dedup): use Map<String,List<String>> for fieldNameMapping to handle alias collision#5593
Conversation
…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>
PR Reviewer Guide 🔍(Review updated until commit 92e5e63)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 92e5e63 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit d3e62b5
Suggestions up to commit fd18a7a
|
* AggregateAnalyzer: guard against a null return from inferNamedField before calling getRootName() (defensive; the method returns non-null for RexInputRef today but the null check makes the contract explicit). * TopHitsParser.applyFieldNameMapping: replace the containsKey+get double lookup with a single map.get() call; null value + absent key is distinguished via containsKey only when value is null, eliminating the redundant containsKey in the common (non-null value) path. Fixes opensearch-project#5150 Fixes opensearch-project#5197 Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
|
Persistent review updated to latest commit d3e62b5 |
The test data (duplication_nullable.json) has category=Y rows in this
order: A (id 2), A (id 3), null (id 8), A (id 12), B (id 15).
'dedup 1 category' keeps the FIRST occurrence per category, which has
name=A for category Y, not B.
Update expected rows: rows("Y","B") -> rows("Y","A") and
rows("Y","B","B") -> rows("Y","A","A").
Signed-off-by: RadhaKrishnan Rajendran <gingeekrishna@gmail.com>
Signed-off-by: Radhakrishnan Pachyappan <gingeekrishna@gmail.com>
|
Persistent review updated to latest commit 92e5e63 |
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