com.hazelcast.org.apache.calcite.plan.RelOptPredicateList Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.org.apache.calcite.plan;
import com.hazelcast.org.apache.calcite.rex.RexBuilder;
import com.hazelcast.org.apache.calcite.rex.RexCall;
import com.hazelcast.org.apache.calcite.rex.RexLiteral;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.sql.SqlKind;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.ImmutableMap;
import com.hazelcast.org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Collection;
import java.util.Objects;
/**
* Predicates that are known to hold in the output of a particular relational
* expression.
*
* Pulled up predicates (field {@link #pulledUpPredicates} are
* predicates that apply to every row output by the relational expression. They
* are inferred from the input relational expression(s) and the relational
* operator.
*
*
For example, if you apply {@code Filter(x > 1)} to a relational
* expression that has a predicate {@code y < 10} then the pulled up predicates
* for the Filter are {@code [y < 10, x > 1]}.
*
*
Inferred predicates only apply to joins. If there there is a
* predicate on the left input to a join, and that predicate is over columns
* used in the join condition, then a predicate can be inferred on the right
* input to the join. (And vice versa.)
*
*
For example, in the query
*
SELECT *
* FROM emp
* JOIN dept ON emp.deptno = dept.deptno
* WHERE emp.gender = 'F' AND emp.deptno < 10
* we have
*
* - left: {@code Filter(Scan(EMP), deptno < 10},
* predicates: {@code [deptno < 10]}
*
- right: {@code Scan(DEPT)}, predicates: {@code []}
*
- join: {@code Join(left, right, emp.deptno = dept.deptno},
* leftInferredPredicates: [],
* rightInferredPredicates: [deptno < 10],
* pulledUpPredicates: [emp.gender = 'F', emp.deptno < 10,
* emp.deptno = dept.deptno, dept.deptno < 10]
*
*
* Note that the predicate from the left input appears in
* {@code rightInferredPredicates}. Predicates from several sources appear in
* {@code pulledUpPredicates}.
*/
public class RelOptPredicateList {
private static final ImmutableList EMPTY_LIST = ImmutableList.of();
public static final RelOptPredicateList EMPTY =
new RelOptPredicateList(EMPTY_LIST, EMPTY_LIST, EMPTY_LIST,
ImmutableMap.of());
/** Predicates that can be pulled up from the relational expression and its
* inputs. */
public final ImmutableList pulledUpPredicates;
/** Predicates that were inferred from the right input.
* Empty if the relational expression is not a join. */
public final ImmutableList leftInferredPredicates;
/** Predicates that were inferred from the left input.
* Empty if the relational expression is not a join. */
public final ImmutableList rightInferredPredicates;
/** A map of each (e, constant) pair that occurs within
* {@link #pulledUpPredicates}. */
public final ImmutableMap constantMap;
private RelOptPredicateList(ImmutableList pulledUpPredicates,
ImmutableList leftInferredPredicates,
ImmutableList rightInferredPredicates,
ImmutableMap constantMap) {
this.pulledUpPredicates = Objects.requireNonNull(pulledUpPredicates, "pulledUpPredicates");
this.leftInferredPredicates =
Objects.requireNonNull(leftInferredPredicates, "leftInferredPredicates");
this.rightInferredPredicates =
Objects.requireNonNull(rightInferredPredicates, "rightInferredPredicates");
this.constantMap = Objects.requireNonNull(constantMap, "constantMap");
}
/** Creates a RelOptPredicateList with only pulled-up predicates, no inferred
* predicates.
*
* Use this for relational expressions other than joins.
*
* @param pulledUpPredicates Predicates that apply to the rows returned by the
* relational expression
*/
public static RelOptPredicateList of(RexBuilder rexBuilder,
Iterable pulledUpPredicates) {
ImmutableList pulledUpPredicatesList =
ImmutableList.copyOf(pulledUpPredicates);
if (pulledUpPredicatesList.isEmpty()) {
return EMPTY;
}
return of(rexBuilder, pulledUpPredicatesList, EMPTY_LIST, EMPTY_LIST);
}
/**
* Returns true if given predicate list is empty.
* @param value input predicate list
* @return true if all the predicates are empty or if the argument is null
*/
public static boolean isEmpty(@Nullable RelOptPredicateList value) {
if (value == null || value == EMPTY) {
return true;
}
return value.constantMap.isEmpty()
&& value.leftInferredPredicates.isEmpty()
&& value.rightInferredPredicates.isEmpty()
&& value.pulledUpPredicates.isEmpty();
}
/** Creates a RelOptPredicateList for a join.
*
* @param rexBuilder Rex builder
* @param pulledUpPredicates Predicates that apply to the rows returned by the
* relational expression
* @param leftInferredPredicates Predicates that were inferred from the right
* input
* @param rightInferredPredicates Predicates that were inferred from the left
* input
*/
public static RelOptPredicateList of(RexBuilder rexBuilder,
Iterable pulledUpPredicates,
Iterable leftInferredPredicates,
Iterable rightInferredPredicates) {
final ImmutableList pulledUpPredicatesList =
ImmutableList.copyOf(pulledUpPredicates);
final ImmutableList leftInferredPredicateList =
ImmutableList.copyOf(leftInferredPredicates);
final ImmutableList rightInferredPredicatesList =
ImmutableList.copyOf(rightInferredPredicates);
if (pulledUpPredicatesList.isEmpty()
&& leftInferredPredicateList.isEmpty()
&& rightInferredPredicatesList.isEmpty()) {
return EMPTY;
}
final ImmutableMap constantMap =
RexUtil.predicateConstants(RexNode.class, rexBuilder,
pulledUpPredicatesList);
return new RelOptPredicateList(pulledUpPredicatesList,
leftInferredPredicateList, rightInferredPredicatesList, constantMap);
}
@Override public String toString() {
final StringBuilder b = new StringBuilder("{");
append(b, "pulled", pulledUpPredicates);
append(b, "left", leftInferredPredicates);
append(b, "right", rightInferredPredicates);
append(b, "constants", constantMap.entrySet());
return b.append("}").toString();
}
private static void append(StringBuilder b, String key, Collection value) {
if (!value.isEmpty()) {
if (b.length() > 1) {
b.append(", ");
}
b.append(key);
b.append(value);
}
}
public RelOptPredicateList union(RexBuilder rexBuilder,
RelOptPredicateList list) {
if (this == EMPTY) {
return list;
} else if (list == EMPTY) {
return this;
} else {
return RelOptPredicateList.of(rexBuilder,
concat(pulledUpPredicates, list.pulledUpPredicates),
concat(leftInferredPredicates, list.leftInferredPredicates),
concat(rightInferredPredicates, list.rightInferredPredicates));
}
}
/** Concatenates two immutable lists, avoiding a copy it possible. */
private static ImmutableList concat(ImmutableList list1,
ImmutableList list2) {
if (list1.isEmpty()) {
return list2;
} else if (list2.isEmpty()) {
return list1;
} else {
return ImmutableList.builder().addAll(list1).addAll(list2).build();
}
}
public RelOptPredicateList shift(RexBuilder rexBuilder, int offset) {
return RelOptPredicateList.of(rexBuilder,
RexUtil.shift(pulledUpPredicates, offset),
RexUtil.shift(leftInferredPredicates, offset),
RexUtil.shift(rightInferredPredicates, offset));
}
/** Returns whether an expression is effectively NOT NULL due to an
* {@code e IS NOT NULL} condition in this predicate list. */
public boolean isEffectivelyNotNull(RexNode e) {
if (!e.getType().isNullable()) {
return true;
}
for (RexNode p : pulledUpPredicates) {
if (p.getKind() == SqlKind.IS_NOT_NULL
&& ((RexCall) p).getOperands().get(0).equals(e)) {
return true;
}
}
if (SqlKind.COMPARISON.contains(e.getKind())) {
// A comparison with a (non-null) literal, such as 'ref < 10', is not null if 'ref'
// is not null.
RexCall call = (RexCall) e;
if (call.getOperands().get(1) instanceof RexLiteral
&& !((RexLiteral) call.getOperands().get(1)).isNull()) {
return isEffectivelyNotNull(call.getOperands().get(0));
}
}
return false;
}
}