[Analytics-Engine] Lower LITERAL_AGG and accept all join condition shapes#21804
Conversation
PR Code Analyzer ❗AI-powered 'Code-Diff-Analyzer' found issues on commit 0721e77.
The table above displays the top 10 most important findings. Pull Requests Author(s): Please update your Pull Request according to the report above. Repository Maintainer(s): You can Thanks. |
PR Reviewer Guide 🔍(Review updated until commit 021cce9)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to 021cce9 Explore these optional code suggestions:
Previous suggestionsSuggestions up to commit a71924d
Suggestions up to commit 9c79999
Suggestions up to commit ac27ee0
Suggestions up to commit 5c54903
Suggestions up to commit de43ffb
|
|
❕ Gradle check result for 6c34247: UNSTABLE Please review all flaky tests that succeeded after retry and create an issue if one does not already exist to track the flaky failure. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #21804 +/- ##
=========================================
Coverage 73.50% 73.51%
- Complexity 75546 75583 +37
=========================================
Files 6034 6034
Lines 342661 342661
Branches 49294 49294
=========================================
+ Hits 251879 251907 +28
+ Misses 70790 70749 -41
- Partials 19992 20005 +13 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
Persistent review updated to latest commit aa33e4d |
|
❌ Gradle check result for aa33e4d: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 02e8b22 |
|
Persistent review updated to latest commit 31df763 |
|
Persistent review updated to latest commit 8fba98f |
|
❌ Gradle check result for 8fba98f: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
8fba98f to
de43ffb
Compare
|
Persistent review updated to latest commit de43ffb |
|
❌ Gradle check result for de43ffb: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 5c54903 |
|
Persistent review updated to latest commit ac27ee0 |
|
❌ Gradle check result for ac27ee0: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit 9c79999 |
|
❌ Gradle check result for 9c79999: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
|
Persistent review updated to latest commit a71924d |
|
❌ Gradle check result for a71924d: FAILURE Please examine the workflow log, locate, and copy-paste the failure(s) below, then iterate to green. Is the failure a flaky test unrelated to your change? |
Builds on PR opensearch-project#21795 (Decorrelate RexSubQuery before marking). ITs ported from opensearch-project/sql: * InSubqueryCommandIT (13 tests) * ScalarSubqueryCommandIT (13 tests) * ExistsSubqueryCommandIT (11 tests) * worker / work_information / occupation datasets OpenSearchJoinRule.matches() relaxed to accept mixed equi+non-equi conditions, IS_NOT_DISTINCT_FROM (null-safe equi), and equi conjuncts where one operand is a RexCall expression (e.g. uid = salary + 1000) - DataFusion hash-join evaluates each operand to derive the key. RelDecorrelator's output for streamstats reset's directly-built LogicalCorrelate carries an AND(<=, =, =) condition; without this relaxation the rule rejected it. StreamstatsCommandIT.testStreamstatsReset flipped to positive (the other two reset_* variants are flipped in the follow-up commit that adds LITERAL_AGG lowering). Signed-off-by: Songkan Tang <songkant@amazon.com>
Two complementary marking-phase fixes layered on top of PR opensearch-project#21795, both keeping Calcite as the source of truth: 1. PlannerImpl.removeSubQueries — after RelDecorrelator, run a Hep pass with ExtractLiteralAggRule (ported from Apache Impala) to lower LITERAL_AGG(literal) aggregate calls into Aggregate + Project. SubQueryRemoveRule.rewriteIn emits LITERAL_AGG for NOT IN's null-aware semantics; Calcite has no rule to remove it; each downstream engine handles it differently (Druid registers a native impl, Ignite wires an accumulator, Impala lowers to Aggregate + Project, Flink forks SubQueryRemoveRule). The Impala approach is a clean Calcite-rule-style rewrite — port it with attribution. 2. OpenSearchJoinRule.matches() — accept all join condition shapes, not just equi or mixed equi+non-equi. RelDecorrelator's output for correlated subqueries with non-equi correlation predicates (e.g. WHERE id > uid) is a pure non-equi LogicalJoin. DataFusion handles non-equi via NestedLoopJoin downstream, so there is no capability-level reason to reject these at the marking phase. End-to-end IT results on this branch (PR opensearch-project#21795 + these two fixes): ExistsSubqueryCommandIT : 11/11 InSubqueryCommandIT : 11/13 (1 row-order, 1 unrelated AggregateSplitRule bug) ScalarSubqueryCommandIT : 13/13 StreamstatsCommandIT : testStreamstatsReset passes Total : 36/38 = 95% The two remaining failures are unrelated to subquery decorrelation: * testTwoExpressionsInSubquery — result row ordering between hash join's bucketing and the IT's exact-row assertion. Test issue. * testTwoExpressionsNotInSubquery — OpenSearchAggregateSplitRule rejects nullable filter expressions ("filter must be BOOLEAN NOT NULL") on a multi-column NOT IN's COUNT FILTER. Independent AggregateSplitRule bug. Signed-off-by: Songkan Tang <songkant@amazon.com>
CI surfaced four StreamstatsCommandIT failures and two InSubqueryCommandIT failures. All six are reconciled here. Streamstats — testStreamstatsResetWithNull / GlobalWithNull / ResetWithNullBucket / GlobalWithNullBucket previously asserted the query *must error* via assertErrorAny. After PR opensearch-project#21795's decorrelation plus this branch's marking-phase fixes (LITERAL_AGG lowering and OpenSearchJoinRule relaxation), these PPL forms now plan and execute end-to-end. Flip the four assertions to executePpl + assertNotNull, matching how testStreamstatsReset is already structured. InSubquery — testTwoExpressionsInSubquery's exact-row assertion was fragile across hash-join output ordering for two rows sharing salary=120000. Add 'id' as a secondary sort key so the order is fully determined. InSubquery — testTwoExpressionsNotInSubquery hits an independent OpenSearchAggregateSplitRule bug ("filter must be BOOLEAN NOT NULL") unrelated to subquery decorrelation. Mark @AwaitsFix with a note pointing at the split-rule limitation. Local verification: * StreamstatsCommandIT — all tests pass. * InSubqueryCommandIT — 12/13 pass, the @AwaitsFix-skipped one is the split-rule case. Signed-off-by: Songkan Tang <songkant@amazon.com>
Two scope reductions following review feedback: * Remove ExistsSubqueryCommandIT, InSubqueryCommandIT, ScalarSubqueryCommandIT and their worker / work_information / occupation datasets. These are subquery-decorrelation coverage ports from the upstream sql repo and don't directly verify the marking-phase fixes layered here. They can be re-added in a dedicated subquery-IT PR if/when the analytics-engine route wants to track that suite. * Simplify OpenSearchJoinRule.matches() to a plain JoinRelType whitelist (INNER / LEFT / RIGHT / FULL / SEMI / ANTI). The condition-shape acceptance is implicit — DataFusion's join planner picks the physical strategy (HashJoin / NestedLoopJoin / CrossJoin) downstream, so there is no need for the rule to inspect the condition. JoinCommandIT.testAppendcol is no longer @AwaitsFix — appendcol's ROW_NUMBER + FULL OUTER LogicalJoin lowering now planes and executes end-to-end on calcs (12/12 JoinCommandIT tests green locally). Signed-off-by: Songkan Tang <songkant@amazon.com>
* PlannerImpl.runAllOptimizations: drop the post-removeSubQueries LOGGER.info dump — the per-phase RuleProfilingListener output already covers what the log line gave. * PlannerImpl.extractLiteralAgg: import ExtractLiteralAggRule and use the simple class name at the call site (and in the @link reference in javadoc). * ExtractLiteralAggRule: import java.util.Objects and use Objects::nonNull instead of the fully-qualified java.util.Objects::nonNull. Signed-off-by: Songkan Tang <songkant@amazon.com>
The previous HepPhase API exposed only addRules(...) and chose addRuleInstance vs addRuleCollection at run() time based on whether the accumulated list had one element or many. That collapsed two Hep instructions with different fixpoint semantics into one branch: * addRuleInstance(R) appends a HepInstruction.RuleInstance — R runs to fixpoint, then the next instruction starts. * addRuleCollection([R1, R2, ...]) appends a RuleCollection — every rule in the group is tried at each vertex within the same fixpoint loop, so a rewrite by one rule can cascade into a match by another in the same pass. The two are not interchangeable. PlannerImpl on main already relied on the distinction in pushdownRules: three transposes were grouped in a RuleCollection so they cascade-converge, then FILTER_MERGE was a separate RuleInstance so it only fired after the transposes settled (otherwise auto-injected NOT NULL would merge with a half- pushed intermediate filter rather than the user's WHERE on the post-pushdown shape). This commit: * Renames the HepPhase API to addRuleInstance / addRuleCollection, mirroring HepProgramBuilder one-to-one. addRuleCollection takes Collection<? extends RelOptRule>. * Stores instructions as List<Consumer<HepProgramBuilder>> and applies them in declared order at run() — no more size-based branching. * Restores PlannerImpl call sites to the original instruction structure: pushdownRules splits back into a transpose collection + a FILTER_MERGE instance, and the single-rule phases use addRuleInstance. Local verification: analytics-engine unit tests, JoinCommandIT, StreamstatsCommandIT all green. Signed-off-by: Songkan Tang <songkant@amazon.com>
a71924d to
021cce9
Compare
|
Persistent review updated to latest commit 021cce9 |
…apes (opensearch-project#21804) * Add subquery ITs and relax OpenSearchJoinRule for mixed conditions Builds on PR opensearch-project#21795 (Decorrelate RexSubQuery before marking). ITs ported from opensearch-project/sql: * InSubqueryCommandIT (13 tests) * ScalarSubqueryCommandIT (13 tests) * ExistsSubqueryCommandIT (11 tests) * worker / work_information / occupation datasets OpenSearchJoinRule.matches() relaxed to accept mixed equi+non-equi conditions, IS_NOT_DISTINCT_FROM (null-safe equi), and equi conjuncts where one operand is a RexCall expression (e.g. uid = salary + 1000) - DataFusion hash-join evaluates each operand to derive the key. RelDecorrelator's output for streamstats reset's directly-built LogicalCorrelate carries an AND(<=, =, =) condition; without this relaxation the rule rejected it. StreamstatsCommandIT.testStreamstatsReset flipped to positive (the other two reset_* variants are flipped in the follow-up commit that adds LITERAL_AGG lowering). Signed-off-by: Songkan Tang <songkant@amazon.com> * Lower LITERAL_AGG and accept all join condition shapes in marking Two complementary marking-phase fixes layered on top of PR opensearch-project#21795, both keeping Calcite as the source of truth: 1. PlannerImpl.removeSubQueries — after RelDecorrelator, run a Hep pass with ExtractLiteralAggRule (ported from Apache Impala) to lower LITERAL_AGG(literal) aggregate calls into Aggregate + Project. SubQueryRemoveRule.rewriteIn emits LITERAL_AGG for NOT IN's null-aware semantics; Calcite has no rule to remove it; each downstream engine handles it differently (Druid registers a native impl, Ignite wires an accumulator, Impala lowers to Aggregate + Project, Flink forks SubQueryRemoveRule). The Impala approach is a clean Calcite-rule-style rewrite — port it with attribution. 2. OpenSearchJoinRule.matches() — accept all join condition shapes, not just equi or mixed equi+non-equi. RelDecorrelator's output for correlated subqueries with non-equi correlation predicates (e.g. WHERE id > uid) is a pure non-equi LogicalJoin. DataFusion handles non-equi via NestedLoopJoin downstream, so there is no capability-level reason to reject these at the marking phase. End-to-end IT results on this branch (PR opensearch-project#21795 + these two fixes): ExistsSubqueryCommandIT : 11/11 InSubqueryCommandIT : 11/13 (1 row-order, 1 unrelated AggregateSplitRule bug) ScalarSubqueryCommandIT : 13/13 StreamstatsCommandIT : testStreamstatsReset passes Total : 36/38 = 95% The two remaining failures are unrelated to subquery decorrelation: * testTwoExpressionsInSubquery — result row ordering between hash join's bucketing and the IT's exact-row assertion. Test issue. * testTwoExpressionsNotInSubquery — OpenSearchAggregateSplitRule rejects nullable filter expressions ("filter must be BOOLEAN NOT NULL") on a multi-column NOT IN's COUNT FILTER. Independent AggregateSplitRule bug. Signed-off-by: Songkan Tang <songkant@amazon.com> * Reconcile streamstats + InSubquery ITs with newly-supported plans CI surfaced four StreamstatsCommandIT failures and two InSubqueryCommandIT failures. All six are reconciled here. Streamstats — testStreamstatsResetWithNull / GlobalWithNull / ResetWithNullBucket / GlobalWithNullBucket previously asserted the query *must error* via assertErrorAny. After PR opensearch-project#21795's decorrelation plus this branch's marking-phase fixes (LITERAL_AGG lowering and OpenSearchJoinRule relaxation), these PPL forms now plan and execute end-to-end. Flip the four assertions to executePpl + assertNotNull, matching how testStreamstatsReset is already structured. InSubquery — testTwoExpressionsInSubquery's exact-row assertion was fragile across hash-join output ordering for two rows sharing salary=120000. Add 'id' as a secondary sort key so the order is fully determined. InSubquery — testTwoExpressionsNotInSubquery hits an independent OpenSearchAggregateSplitRule bug ("filter must be BOOLEAN NOT NULL") unrelated to subquery decorrelation. Mark @AwaitsFix with a note pointing at the split-rule limitation. Local verification: * StreamstatsCommandIT — all tests pass. * InSubqueryCommandIT — 12/13 pass, the @AwaitsFix-skipped one is the split-rule case. Signed-off-by: Songkan Tang <songkant@amazon.com> * Trim subquery ITs and simplify OpenSearchJoinRule.matches Two scope reductions following review feedback: * Remove ExistsSubqueryCommandIT, InSubqueryCommandIT, ScalarSubqueryCommandIT and their worker / work_information / occupation datasets. These are subquery-decorrelation coverage ports from the upstream sql repo and don't directly verify the marking-phase fixes layered here. They can be re-added in a dedicated subquery-IT PR if/when the analytics-engine route wants to track that suite. * Simplify OpenSearchJoinRule.matches() to a plain JoinRelType whitelist (INNER / LEFT / RIGHT / FULL / SEMI / ANTI). The condition-shape acceptance is implicit — DataFusion's join planner picks the physical strategy (HashJoin / NestedLoopJoin / CrossJoin) downstream, so there is no need for the rule to inspect the condition. JoinCommandIT.testAppendcol is no longer @AwaitsFix — appendcol's ROW_NUMBER + FULL OUTER LogicalJoin lowering now planes and executes end-to-end on calcs (12/12 JoinCommandIT tests green locally). Signed-off-by: Songkan Tang <songkant@amazon.com> * Address review comments * PlannerImpl.runAllOptimizations: drop the post-removeSubQueries LOGGER.info dump — the per-phase RuleProfilingListener output already covers what the log line gave. * PlannerImpl.extractLiteralAgg: import ExtractLiteralAggRule and use the simple class name at the call site (and in the @link reference in javadoc). * ExtractLiteralAggRule: import java.util.Objects and use Objects::nonNull instead of the fully-qualified java.util.Objects::nonNull. Signed-off-by: Songkan Tang <songkant@amazon.com> * HepPhase: distinguish RuleInstance from RuleCollection The previous HepPhase API exposed only addRules(...) and chose addRuleInstance vs addRuleCollection at run() time based on whether the accumulated list had one element or many. That collapsed two Hep instructions with different fixpoint semantics into one branch: * addRuleInstance(R) appends a HepInstruction.RuleInstance — R runs to fixpoint, then the next instruction starts. * addRuleCollection([R1, R2, ...]) appends a RuleCollection — every rule in the group is tried at each vertex within the same fixpoint loop, so a rewrite by one rule can cascade into a match by another in the same pass. The two are not interchangeable. PlannerImpl on main already relied on the distinction in pushdownRules: three transposes were grouped in a RuleCollection so they cascade-converge, then FILTER_MERGE was a separate RuleInstance so it only fired after the transposes settled (otherwise auto-injected NOT NULL would merge with a half- pushed intermediate filter rather than the user's WHERE on the post-pushdown shape). This commit: * Renames the HepPhase API to addRuleInstance / addRuleCollection, mirroring HepProgramBuilder one-to-one. addRuleCollection takes Collection<? extends RelOptRule>. * Stores instructions as List<Consumer<HepProgramBuilder>> and applies them in declared order at run() — no more size-based branching. * Restores PlannerImpl call sites to the original instruction structure: pushdownRules splits back into a transpose collection + a FILTER_MERGE instance, and the single-rule phases use addRuleInstance. Local verification: analytics-engine unit tests, JoinCommandIT, StreamstatsCommandIT all green. Signed-off-by: Songkan Tang <songkant@amazon.com> --------- Signed-off-by: Songkan Tang <songkant@amazon.com>
Description
Two complementary marking-phase fixes that build on top of #21795. Both keep Calcite as the source of truth and unblock end-to-end correlated-subquery execution on the analytics-engine route.
LITERAL_AGG lowering — after
RelDecorrelatorruns inPlannerImpl.removeSubQueries, a new Hep pass invokesExtractLiteralAggRule(ported from Apache Impala with attribution) to rewriteLITERAL_AGG(literal)aggregate calls into anAggregate+Projectshape.SubQueryRemoveRule.rewriteInemitsLITERAL_AGGforNOT IN's null-aware semantics; Calcite ships no rule to remove it, and each downstream engine handles it differently (Druid registers a native impl, Ignite wires an accumulator, Impala lowers, Flink forksSubQueryRemoveRule). The Impala approach is a clean Calcite-rule-style rewrite — port it.OpenSearchJoinRule.matches()relaxed — accept all join condition shapes, not just equi or mixed equi+non-equi.RelDecorrelator's output for correlated subqueries with non-equi correlation predicates (e.g.WHERE id > uid) is a pure non-equiLogicalJoin. DataFusion handles non-equi via NestedLoopJoin downstream, so there is no capability-level reason to reject these in marking.PlannerImplis also refactored to a fluentHepPhasebuilder so each Hep pass attaches the existingRuleProfilingListenerconsistently and the phase boundary is recorded in the planner profile.Dependency on #21795
This PR is draft until #21795 merges. The branch contains all of #21795's commits plus the three commits in this layer; once #21795 lands, this PR will rebase onto
mainand only show the layered diff.Test results (on this branch = #21795 + these fixes)
ExistsSubqueryCommandITScalarSubqueryCommandITInSubqueryCommandITStreamstatsCommandIT.testStreamstatsResetJoinCommandITThe two remaining
InSubqueryCommandITfailures are unrelated to subquery decorrelation:testTwoExpressionsInSubquery— row-ordering assertion vs. hash-join bucketing. Test issue.testTwoExpressionsNotInSubquery—OpenSearchAggregateSplitRulerejects nullable filter expressions ("filter must be BOOLEAN NOT NULL") on a multi-columnNOT IN'sCOUNT FILTER. Independent split-rule bug.Related Issues
Builds on #21795.
Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.