[CALCITE-7442] Getting Wrong index of Correlated variable inside Subquery after FilterJoinRule#4840
[CALCITE-7442] Getting Wrong index of Correlated variable inside Subquery after FilterJoinRule#4840yashlimbad wants to merge 3 commits intoapache:mainfrom
Conversation
53f3132 to
fb20a82
Compare
|
There is an error in the CI that needs to be resolved. |
fb20a82 to
e6ebfcc
Compare
|
Updated the Code. |
There was a problem hiding this comment.
Pull request overview
Fixes a decorrelation/planning correctness issue (CALCITE-7442) where a correlated variable’s field index inside a subquery can become incorrect after FilterJoinRule pushes filters.
Changes:
- Add a regression test that exercises
FILTER_INTO_JOIN+FILTER_SUB_QUERY_TO_CORRELATEand then decorrelates, asserting expected plans. - Extend
FilterJoinRuleto propagate correlation-variable sets when creating pushed-down filters (and when keeping an above-join filter). - Enhance
RelOptUtil.classifyFiltersshifting logic so correlated field accesses insideRexSubQuery.relcan be adjusted during filter pushdown.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java | Adds a regression test covering correlated variable index behavior through filter pushdown + decorrelation. |
| core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java | Tracks correlation variables when constructing new Filters after pushdown. |
| core/src/main/java/org/apache/calcite/plan/RelOptUtil.java | Extends filter-shifting to also adjust correlated field accesses inside subqueries. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
Outdated
Show resolved
Hide resolved
core/src/main/java/org/apache/calcite/rel/rules/FilterJoinRule.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
Outdated
Show resolved
Hide resolved
|
I'm not sure if these comments will be helpful, as I'm not very familiar with this specific area. If someone more knowledgeable doesn't review this PR within the next few days, I'll give it a try myself. |
|
some comments were looking helpful so I updated code now will check by running build if it goes through and push accordingly! 😄 it's fine if someone reviews code in few days, but I feel @julianhyde would be the best to review this because I see only his commit on RelOptUtil which I updated which is called from FilterJoinRule |
2c9af44 to
815c57d
Compare
|
sometimes the tests passes sometimes fails randomly with below error in CI even tho |
e35441e to
55e4b1d
Compare
|
Hey @xiedeyantu , |
|
Sorry, could I get back to you in a few days? I'm currently on vacation and don't have access to a computer for debugging. |
|
sure, no problem! |
|
@yashlimbad |
|
my bad. updated @caicancai ! |
| * @param adjustments the amount to adjust each field by | ||
| * @param offset the amount to shift field accesses by when | ||
| * rewriting correlated subqueries | ||
| * @param correlateVariableChild the child relation providing the |
There was a problem hiding this comment.
The code comment format here is strange.
There was a problem hiding this comment.
the variable name is big here, that's why it's going into it's doc. any suggestions on formatting?
There was a problem hiding this comment.
Indeed, I'll take a look at other Calcite code later to see if there are any good solutions.
There was a problem hiding this comment.
Perhaps changing to a shorter, more concise variable name would solve the problem. 🤔
There was a problem hiding this comment.
got it, will get back on this tomorrow
There was a problem hiding this comment.
using a shorter variable name to make formatting of comments nice is not a good reason.
There was a problem hiding this comment.
Thanks for the reminder, I'll look into whether there's a better way to handle this tomorrow.
| @Override public RexNode visitSubQuery(RexSubQuery subQuery) { | ||
| boolean[] update = {false}; | ||
| List<RexNode> clonedOperands = visitList(subQuery.operands, update); | ||
| if (update[0]) { |
There was a problem hiding this comment.
I suspect there might be an issue with the EXISTS subquery, but I'm not entirely sure. Could you add a similar test?
There was a problem hiding this comment.
EXISTS fails similar to IN clause, nice catch! thanks for this. will work on it
There was a problem hiding this comment.
Besides update[0], is there a better representation? Could you explain why it must be update[0]?
| subQuery = subQuery.clone(subQuery.getType(), clonedOperands); | ||
| final Set<CorrelationId> variablesSet = RelOptUtil.getVariablesUsed(subQuery.rel); | ||
| if (!variablesSet.isEmpty() && correlateVariableChild != null) { | ||
| CorrelationId id = Iterables.getOnlyElement(variablesSet); |
There was a problem hiding this comment.
Are you assuming there's only one correlation variable?
There was a problem hiding this comment.
my bad, updating whole variable set now
55e4b1d to
4f4341c
Compare
|
I have some questions that I might need to confirm with debugging. I will try my best to complete the review this week. |
|
Great! thank you @caicancai |
xiedeyantu
left a comment
There was a problem hiding this comment.
LGTM! Please wait for @caicancai to finish the review. I have no further suggestions.
|
|
||
| @Override public RexNode visitSubQuery(RexSubQuery subQuery) { | ||
| boolean[] update = {false}; | ||
| List<RexNode> clonedOperands = visitList(subQuery.operands, update); |
There was a problem hiding this comment.
boolean[] update = {false}; Is it necessary?
There was a problem hiding this comment.
yes,
visitList(
List<? extends RexNode> exprs, boolean @Nullable [] update
accepts list of boolean and updates 0th index to true if updated.
pattern is same everywhere
| @Override public RexNode visitSubQuery(RexSubQuery subQuery) { | ||
| boolean[] update = {false}; | ||
| List<RexNode> clonedOperands = visitList(subQuery.operands, update); | ||
| if (update[0]) { |
There was a problem hiding this comment.
Besides update[0], is there a better representation? Could you explain why it must be update[0]?
silundong
left a comment
There was a problem hiding this comment.
Pushing down correlated subqueries may carry risks; sorry I didn't provide concrete cases. The logic here is indeed complex. Perhaps follow the logic for handling the top-level Filter in FilterJoinRule—i.e., not pushing down correlated subqueries—would be a good option.
core/src/test/java/org/apache/calcite/sql2rel/RelDecorrelatorTest.java
Outdated
Show resolved
Hide resolved
the subquery is inside Join condition, not inside filter on top of join. and decorrelating join's condition is harder and can complicate resulting tree sometime. on the other hand decorrelating filter is logically better |
Making |
I agree that subquery remove rule will do the same, but what if someone invokes |
Most of Calcite is based on the assumption that subquery removal is always a good first step, and further rules don't have to deal with subqueries. The assumption looks reasonable to me, and I don't see a valid reason to do otherwise here. More code and more complex support for rules makes them harder to maintain, evolve and understand, without a compelling reason we should strive to keep things simple IMO. |
@asolimando I think the concern here is a bit narrower than "every rule should support subqueries". Once FilterJoinRule chooses to rewrite a filter that contains a correlated subquery, it is already operating on that structure. At that point, I think the rule should do one of two things:
My concern with the current behavior is that it rewrites the predicate but can leave correlated field references inconsistent, which means correctness depends on a later SubQueryRemoveRule pass to repair the intermediate state. In other words, the issue is not only whether SubQueryRemoveRule can eventually handle it, but whether FilterJoinRule should emit an invalid expression for a shape it has already transformed. The PR description itself is fixing exactly that kind of planning/decorrelation correctness issue around correlated variable field indices after filter pushdown. So from my perspective the acceptable outcomes are:
If the preference is to keep subquery handling centralized, I'm happy to revise this toward the conservative bail-out approach and avoid pushing conjuncts that contain correlated references. |
4f4341c to
75f3b7a
Compare
|
@yashlimbad Unless absolutely necessary, please avoid force-pushing during review, as it makes it difficult for reviewers to compare incremental diffs. You can push as many commits as you need and squash them before the final merge. |
|
oh, my bad @silundong, I will commit separately from next time. if you want last commit diff then here it is |
|
No worries. I will complete the review over the next few days. |
|
Thank you! @silundong |
silundong
left a comment
There was a problem hiding this comment.
I want to restate my concern that pushing down correlated subqueries may introduce risks and make the logic significantly more complex; therefore, avoiding such pushdowns is a good option.
|
Thank you for your effort. I'm sorry that your implementation hasn't fully convinced me. I've outlined my concerns in detail in the review. If other reviewers accept it, I will respect their decision and have no further objections. |
8ea6009 to
1a2e7c3
Compare
|
@silundong I have addressed your concern mentioned above. we will not push a subquery if it's having a correlation variable. I have updated tests as well accordingly, please review now. |
|
silundong
left a comment
There was a problem hiding this comment.
It appears that making the appropriate changes to RelOptUtil will satisfy our needs; other files do not seem to require additional modifications.
| // binder for the variable above it, so the reference would be stranded. | ||
| // Pushing such a predicate to the RIGHT input is still safe (the right | ||
| // subtree is the natural consumer of the binding established by this | ||
| // join), and it can of course stay on the join itself. |
There was a problem hiding this comment.
Based on past community discussions and the current implementation, the CorrelationId in variablesSet of Join represents the concatenation of the left and right rows; the CorrelationId in Correlate represents rows that are produced by the left side and consumed by the right side.
To avoid confusion, the comment here may need to be adjusted. The core summary of our previous discussion is: pushing down correlated subqueries carries risks and involves extremely complex logic, and therefore pushing down is prohibited.
| private final @Nullable Set<RelDataTypeField> extraFields; | ||
| /** Correlation ids that are considered "local" when analysing | ||
| * sub-queries: only these contribute bits via {@link #visitSubQuery}. | ||
| * If null, all correlation ids encountered inside a sub-query |
There was a problem hiding this comment.
If null, visitSubQuery should return immediately.
| final ImmutableBitSet inputBits = inputFinder.build(); | ||
|
|
||
| // Disable left-push only for filters that reference a CorrelationId | ||
| // bound by this join. |
There was a problem hiding this comment.
Disable pushing down to both, not just to the left side.
| // conjunct lives. The buckets are needed to (a) plumb variablesSet onto the | ||
| // newly-created child Filters and (b) recompute the join's own variablesSet | ||
| // without dropping ids that the surviving join condition still references. | ||
| final Set<CorrelationId> leftVariablesSet = new LinkedHashSet<>(); |
There was a problem hiding this comment.
Maybe the changes in the RelOptUtil are sufficient to meet our needs; no other files need to be modified.
| } | ||
|
|
||
| @Override public Void visitSubQuery(RexSubQuery subQuery) { | ||
| final Set<CorrelationId> variablesSet = RelOptUtil.getVariablesUsed(subQuery.rel); |
There was a problem hiding this comment.
We only care about localCorrelationIds, so if localCorrelationIds is null, return immediately; otherwise, iterate over localCorrelationIds and call RelOptUtil.correlationColumns.
RelOptUtil.getVariablesUsed(subQuery.rel) is unnecessary.
| * reached through a {@link org.apache.calcite.rex.RexFieldAccess}) or | ||
| * transitively via the inner plan of a {@link RexSubQuery}. | ||
| */ | ||
| private static boolean referencesAnyCorrelation(RexNode node, |
There was a problem hiding this comment.
The purpose of RexUtil.CorrelationFinder is similar to this. Would you consider extending it? That would reuse and strengthen the existing capability.



Jira Link
CALCITE-7442
Changes
Adjust offset of correlated variable inside subquery when pushing filter via FilterJoinRule.java