26
26
import org .apache .calcite .rel .core .Join ;
27
27
import org .apache .calcite .rel .core .JoinRelType ;
28
28
import org .apache .calcite .rel .core .Project ;
29
+ import org .apache .calcite .rel .type .RelDataTypeField ;
29
30
import org .apache .calcite .rex .RexCall ;
30
31
import org .apache .calcite .rex .RexNode ;
31
32
import org .apache .calcite .rex .RexVisitorImpl ;
32
33
import org .apache .calcite .sql .SqlKind ;
33
34
import org .apache .calcite .sql .fun .SqlStdOperatorTable ;
35
+ import org .apache .calcite .util .ImmutableBitSet ;
36
+ import org .apache .calcite .util .Util ;
37
+ import org .apache .commons .lang3 .mutable .MutableBoolean ;
34
38
import org .apache .hadoop .hive .ql .optimizer .calcite .HiveCalciteUtil ;
35
39
import org .apache .hadoop .hive .ql .optimizer .calcite .reloperators .HiveAntiJoin ;
36
40
import org .slf4j .Logger ;
39
43
import java .util .ArrayList ;
40
44
import java .util .Collections ;
41
45
import java .util .List ;
42
- import java .util .concurrent . atomic . AtomicBoolean ;
46
+ import java .util .Optional ;
43
47
44
48
/**
45
49
* Planner rule that converts a join plus filter to anti join.
@@ -86,14 +90,17 @@ protected void perform(RelOptRuleCall call, Project project, Filter filter, Join
86
90
87
91
assert (filter != null );
88
92
89
- List <RexNode > filterList = getResidualFilterNodes (filter , join );
90
- if (filterList == null ) {
93
+ ImmutableBitSet rhsFields = HiveCalciteUtil .getRightSideBitset (join );
94
+ Optional <List <RexNode >> optFilterList = getResidualFilterNodes (filter , join , rhsFields );
95
+ if (optFilterList .isEmpty ()) {
91
96
return ;
92
97
}
98
+ List <RexNode > filterList = optFilterList .get ();
93
99
94
100
// If any projection is there from right side, then we can not convert to anti join.
95
- boolean hasProjection = HiveCalciteUtil .hasAnyExpressionFromRightSide (join , project .getProjects ());
96
- if (hasProjection ) {
101
+ ImmutableBitSet projectedFields = RelOptUtil .InputFinder .bits (project .getProjects (), null );
102
+ boolean projectionUsesRHS = projectedFields .intersects (rhsFields );
103
+ if (projectionUsesRHS ) {
97
104
return ;
98
105
}
99
106
@@ -119,13 +126,14 @@ protected void perform(RelOptRuleCall call, Project project, Filter filter, Join
119
126
/**
120
127
* Extracts the non-null filter conditions from given filter node.
121
128
*
122
- * @param filter The filter condition to be checked.
123
- * @param join Join node whose right side has to be searched.
129
+ * @param filter The filter condition to be checked.
130
+ * @param join Join node whose right side has to be searched.
131
+ * @param rhsFields
124
132
* @return null : Anti join condition is not matched for filter.
125
- * Empty list : No residual filter conditions present.
126
- * Valid list containing the filter to be applied after join.
133
+ * Empty list : No residual filter conditions present.
134
+ * Valid list containing the filter to be applied after join.
127
135
*/
128
- private List <RexNode > getResidualFilterNodes (Filter filter , Join join ) {
136
+ private Optional < List <RexNode >> getResidualFilterNodes (Filter filter , Join join , ImmutableBitSet rhsFields ) {
129
137
// 1. If null filter is not present from right side then we can not convert to anti join.
130
138
// 2. If any non-null filter is present from right side, we can not convert it to anti join.
131
139
// 3. Keep other filters which needs to be executed after join.
@@ -135,43 +143,76 @@ private List<RexNode> getResidualFilterNodes(Filter filter, Join join) {
135
143
List <RexNode > aboveFilters = RelOptUtil .conjunctions (filter .getCondition ());
136
144
boolean hasNullFilterOnRightSide = false ;
137
145
List <RexNode > filterList = new ArrayList <>();
146
+ final ImmutableBitSet notNullColumnsFromRightSide = getNotNullColumnsFromRightSide (join );
147
+
138
148
for (RexNode filterNode : aboveFilters ) {
139
- if (filterNode .getKind () == SqlKind .IS_NULL ) {
140
- // Null filter from right side table can be removed and its a pre-condition for anti join conversion.
141
- if (HiveCalciteUtil .hasAllExpressionsFromRightSide (join , Collections .singletonList (filterNode ))
142
- && isStrong (((RexCall ) filterNode ).getOperands ().get (0 ))) {
143
- hasNullFilterOnRightSide = true ;
144
- } else {
145
- filterList .add (filterNode );
146
- }
147
- } else {
148
- if (HiveCalciteUtil .hasAnyExpressionFromRightSide (join , Collections .singletonList (filterNode ))) {
149
- // If some non null condition is present from right side, we can not convert the join to anti join as
150
- // anti join does not project the fields from right side.
151
- return null ;
152
- } else {
153
- filterList .add (filterNode );
154
- }
149
+ final ImmutableBitSet usedFields = RelOptUtil .InputFinder .bits (filterNode );
150
+ boolean usesFieldFromRHS = usedFields .intersects (rhsFields );
151
+
152
+ if (!usesFieldFromRHS ) {
153
+ // Only LHS fields or constants, so the filterNode is part of the residual filter
154
+ filterList .add (filterNode );
155
+ continue ;
156
+ }
157
+
158
+ // In the following we check for filter nodes that let us deduce that
159
+ // "an (originally) not-null column of RHS IS NULL because the LHS row will not be matched"
160
+
161
+ if (filterNode .getKind () != SqlKind .IS_NULL ) {
162
+ return Optional .empty ();
163
+ }
164
+
165
+ boolean usesRHSFieldsOnly = rhsFields .contains (usedFields );
166
+ if (!usesRHSFieldsOnly ) {
167
+ // If there is a mix between LHS and RHS fields, don't convert to anti-join
168
+ return Optional .empty ();
169
+ }
170
+
171
+ // Null filter from right side table can be removed and it is a pre-condition for anti join conversion.
172
+ RexNode arg = ((RexCall ) filterNode ).getOperands ().get (0 );
173
+ if (isStrong (arg , notNullColumnsFromRightSide )) {
174
+ hasNullFilterOnRightSide = true ;
175
+ } else if (!isStrong (arg , rhsFields )) {
176
+ // if all RHS fields are null and the IS NULL is still not fulfilled, bail out
177
+ return Optional .empty ();
155
178
}
156
179
}
157
180
158
181
if (!hasNullFilterOnRightSide ) {
159
- return null ;
182
+ return Optional . empty () ;
160
183
}
161
- return filterList ;
184
+ return Optional . of ( filterList ) ;
162
185
}
163
186
164
- private boolean isStrong ( RexNode rexNode ) {
165
- AtomicBoolean hasCast = new AtomicBoolean ( false );
166
- rexNode . accept ( new RexVisitorImpl < Void >( true ) {
167
- @ Override
168
- public Void visitCall ( RexCall call ) {
169
- if ( call . getKind () == SqlKind . CAST ) {
170
- hasCast . set ( true );
171
- }
172
- return super . visitCall ( call );
187
+ private ImmutableBitSet getNotNullColumnsFromRightSide ( RelNode joinRel ) {
188
+ // we need to shift the indices of the second child to the right
189
+ int shift = ( joinRel . getInput ( 0 )). getRowType (). getFieldCount ();
190
+
191
+ ImmutableBitSet . Builder builder = ImmutableBitSet . builder ();
192
+ List < RelDataTypeField > rhsFields = joinRel . getInput ( 1 ). getRowType (). getFieldList ();
193
+ for ( RelDataTypeField field : rhsFields ) {
194
+ if (! field . getType (). isNullable ()) {
195
+ builder . set ( shift + field . getIndex () );
173
196
}
174
- });
175
- return !hasCast .get () && Strong .isStrong (rexNode );
197
+ }
198
+ return builder .build ();
199
+ }
200
+
201
+ private boolean isStrong (RexNode rexNode , ImmutableBitSet rightSideBitset ) {
202
+ try {
203
+ rexNode .accept (new RexVisitorImpl <Void >(true ) {
204
+ @ Override
205
+ public Void visitCall (RexCall call ) {
206
+ if (call .getKind () == SqlKind .CAST ) {
207
+ throw Util .FoundOne .NULL ;
208
+ }
209
+ return super .visitCall (call );
210
+ }
211
+ });
212
+ } catch (Util .FoundOne e ) {
213
+ // Hive's CAST might introduce NULL for NOT NULL fields
214
+ return false ;
215
+ }
216
+ return Strong .isNull (rexNode , rightSideBitset );
176
217
}
177
218
}
0 commit comments