blob: 22248fbc2ae81f43a42e02e10d194786482515d7 [file] [log] [blame]
/*
* 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 org.apache.drill.exec.planner.common;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.calcite.rex.RexFieldAccess;
import org.apache.drill.metastore.statistics.TableStatisticsKind;
import org.apache.drill.metastore.metadata.TableMetadata;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.hep.HepRelVertex;
import org.apache.calcite.plan.volcano.RelSubset;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.rules.ProjectRemoveRule;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeField;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexVisitor;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.util.Pair;
import org.apache.calcite.util.Util;
import org.apache.drill.common.expression.PathSegment;
import org.apache.drill.common.expression.SchemaPath;
import org.apache.drill.common.types.TypeProtos;
import org.apache.drill.common.types.Types;
import org.apache.drill.exec.planner.logical.DrillRelFactories;
import org.apache.drill.exec.planner.logical.DrillTable;
import org.apache.drill.exec.planner.logical.FieldsReWriterUtil;
import org.apache.drill.exec.planner.logical.DrillTranslatableTable;
import org.apache.drill.exec.planner.physical.PlannerSettings;
import org.apache.drill.exec.resolver.TypeCastRules;
import org.apache.drill.exec.util.Utilities;
import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableList;
import org.apache.drill.shaded.guava.com.google.common.collect.ImmutableMap;
import org.apache.drill.shaded.guava.com.google.common.collect.Lists;
import org.apache.drill.shaded.guava.com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class that is a subset of the RelOptUtil class and is a placeholder for Drill specific
* static methods that are needed during either logical or physical planning.
*/
public abstract class DrillRelOptUtil {
private static final Logger logger = LoggerFactory.getLogger(DrillRelOptUtil.class);
final public static String IMPLICIT_COLUMN = "$drill_implicit_field$";
// Similar to RelOptUtil.areRowTypesEqual() with the additional check for allowSubstring
public static boolean areRowTypesCompatible(
RelDataType rowType1,
RelDataType rowType2,
boolean compareNames,
boolean allowSubstring) {
if (rowType1 == rowType2) {
return true;
}
if (compareNames) {
// if types are not identity-equal, then either the names or
// the types must be different
return false;
}
if (rowType2.getFieldCount() != rowType1.getFieldCount()) {
return false;
}
final List<RelDataTypeField> f1 = rowType1.getFieldList();
final List<RelDataTypeField> f2 = rowType2.getFieldList();
for (Pair<RelDataTypeField, RelDataTypeField> pair : Pair.zip(f1, f2)) {
final RelDataType type1 = pair.left.getType();
final RelDataType type2 = pair.right.getType();
// If one of the types is ANY comparison should succeed
if (type1.getSqlTypeName() == SqlTypeName.ANY
|| type2.getSqlTypeName() == SqlTypeName.ANY) {
continue;
}
if (type1.getSqlTypeName() != type2.getSqlTypeName()) {
if (allowSubstring
&& (type1.getSqlTypeName() == SqlTypeName.CHAR && type2.getSqlTypeName() == SqlTypeName.CHAR)
&& (type1.getPrecision() <= type2.getPrecision())) {
return true;
}
// Check if Drill implicit casting can resolve the incompatibility
List<TypeProtos.MinorType> types = Lists.newArrayListWithCapacity(2);
types.add(Types.getMinorTypeFromName(type1.getSqlTypeName().getName()));
types.add(Types.getMinorTypeFromName(type2.getSqlTypeName().getName()));
return TypeCastRules.getLeastRestrictiveType(types) != null;
}
}
return true;
}
/**
* Returns a relational expression which has the same fields as the
* underlying expression, but the fields have different names.
*
*
* @param rel Relational expression
* @param fieldNames Field names
* @return Renamed relational expression
*/
public static RelNode createRename(
RelNode rel,
final List<String> fieldNames) {
final List<RelDataTypeField> fields = rel.getRowType().getFieldList();
assert fieldNames.size() == fields.size();
final List<RexNode> refs =
new AbstractList<RexNode>() {
public int size() {
return fields.size();
}
public RexNode get(int index) {
return RexInputRef.of(index, fields);
}
};
return DrillRelFactories.LOGICAL_BUILDER
.create(rel.getCluster(), null)
.push(rel)
.projectNamed(refs, fieldNames, true)
.build();
}
public static boolean isTrivialProject(Project project, boolean useNamesInIdentityProjCalc) {
if (!useNamesInIdentityProjCalc) {
return ProjectRemoveRule.isTrivial(project);
} else {
return containIdentity(project.getProjects(), project.getRowType(), project.getInput().getRowType());
}
}
/** Returns a rowType having all unique field name.
*
* @param rowType : input rowType
* @param typeFactory : type factory used to create a new row type.
* @return a rowType having all unique field name.
*/
public static RelDataType uniqifyFieldName(final RelDataType rowType, final RelDataTypeFactory typeFactory) {
return typeFactory.createStructType(RelOptUtil.getFieldTypeList(rowType),
SqlValidatorUtil.uniquify(rowType.getFieldNames(), SqlValidatorUtil.EXPR_SUGGESTER, true));
}
/**
* Returns whether the leading edge of a given array of expressions is
* wholly {@link RexInputRef} objects with types and names corresponding
* to the underlying row type. */
private static boolean containIdentity(List<? extends RexNode> exps,
RelDataType rowType, RelDataType childRowType) {
List<RelDataTypeField> fields = rowType.getFieldList();
List<RelDataTypeField> childFields = childRowType.getFieldList();
int fieldCount = childFields.size();
if (exps.size() != fieldCount) {
return false;
}
for (int i = 0; i < exps.size(); i++) {
RexNode exp = exps.get(i);
if (!(exp instanceof RexInputRef)) {
return false;
}
RexInputRef var = (RexInputRef) exp;
if (var.getIndex() != i) {
return false;
}
if (!fields.get(i).getName().equals(childFields.get(i).getName())) {
return false;
}
if (!fields.get(i).getType().equals(childFields.get(i).getType())) {
return false;
}
}
return true;
}
/**
* Travesal RexNode to find at least one operator in the given collection. Continue search if RexNode has a
* RexInputRef which refers to a RexNode in project expressions.
*
* @param node : RexNode to search
* @param projExprs : the list of project expressions. Empty list means there is No project operator underneath.
* @param operators collection of operators to find
* @return : Return null if there is NONE; return the first appearance of item/flatten RexCall.
*/
public static RexCall findOperators(final RexNode node, final List<RexNode> projExprs, final Collection<String> operators) {
try {
RexVisitor<Void> visitor =
new RexVisitorImpl<Void>(true) {
public Void visitCall(RexCall call) {
if (operators.contains(call.getOperator().getName().toLowerCase())) {
throw new Util.FoundOne(call); /* throw exception to interrupt tree walk (this is similar to
other utility methods in RexUtil.java */
}
return super.visitCall(call);
}
public Void visitInputRef(RexInputRef inputRef) {
if (projExprs.size() == 0 ) {
return super.visitInputRef(inputRef);
} else {
final int index = inputRef.getIndex();
RexNode n = projExprs.get(index);
if (n instanceof RexCall) {
RexCall r = (RexCall) n;
if (operators.contains(r.getOperator().getName().toLowerCase())) {
throw new Util.FoundOne(r);
}
}
return super.visitInputRef(inputRef);
}
}
};
node.accept(visitor);
return null;
} catch (Util.FoundOne e) {
Util.swallow(e, null);
return (RexCall) e.getNode();
}
}
public static boolean isLimit0(RexNode fetch) {
if (fetch != null && fetch.isA(SqlKind.LITERAL)) {
RexLiteral l = (RexLiteral) fetch;
switch (l.getTypeName()) {
case BIGINT:
case INTEGER:
case DECIMAL:
if (((long) l.getValue2()) == 0) {
return true;
}
}
}
return false;
}
/**
* Find whether the given project rel can produce non-scalar output (hence unknown rowcount). This
* would happen if the project has a flatten
* @param project : The project rel
* @return : Return true if the rowcount is unknown. Otherwise, false.
*/
public static boolean isProjectOutputRowcountUnknown(Project project) {
for (RexNode rex : project.getProjects()) {
if (rex instanceof RexCall) {
if ("flatten".equals(((RexCall) rex).getOperator().getName().toLowerCase())) {
return true;
}
}
}
return false;
}
/**
* Find whether the given project rel has unknown output schema. This would happen if the
* project has CONVERT_FROMJSON which can only derive the schema after evaluation is performed
* @param project : The project rel
* @return : Return true if the project output schema is unknown. Otherwise, false.
*/
public static boolean isProjectOutputSchemaUnknown(Project project) {
try {
RexVisitor<Void> visitor =
new RexVisitorImpl<Void>(true) {
public Void visitCall(RexCall call) {
if ("convert_fromjson".equals(call.getOperator().getName().toLowerCase())) {
throw new Util.FoundOne(call); /* throw exception to interrupt tree walk (this is similar to
other utility methods in RexUtil.java */
}
return super.visitCall(call);
}
};
for (RexNode rex : project.getProjects()) {
rex.accept(visitor);
}
} catch (Util.FoundOne e) {
Util.swallow(e, null);
return true;
}
return false;
}
/**
* InputRefVisitor is a utility class used to collect all the RexInputRef nodes in a
* RexNode.
*
*/
public static class InputRefVisitor extends RexVisitorImpl<Void> {
private final List<RexInputRef> inputRefList;
public InputRefVisitor() {
super(true);
inputRefList = new ArrayList<>();
}
public Void visitInputRef(RexInputRef ref) {
inputRefList.add(ref);
return null;
}
public Void visitCall(RexCall call) {
for (RexNode operand : call.operands) {
operand.accept(this);
}
return null;
}
public List<RexInputRef> getInputRefs() {
return inputRefList;
}
}
/**
* For a given row type return a map between old field indices and one index right shifted fields.
* @param rowType : row type to be right shifted.
* @return map: hash map between old and new indices
*/
public static Map<Integer, Integer> rightShiftColsInRowType(RelDataType rowType) {
Map<Integer, Integer> map = new HashMap<>();
int fieldCount = rowType.getFieldCount();
for (int i = 0; i< fieldCount; i++) {
map.put(i, i+1);
}
return map;
}
/**
* Given a list of rexnodes it transforms the rexnodes by changing the expr to use new index mapped to the old index.
* @param builder : RexBuilder from the planner.
* @param exprs: RexNodes to be transformed.
* @param corrMap: Mapping between old index to new index.
* @return
*/
public static List<RexNode> transformExprs(RexBuilder builder, List<RexNode> exprs, Map<Integer, Integer> corrMap) {
List<RexNode> outputExprs = new ArrayList<>();
DrillRelOptUtil.RexFieldsTransformer transformer = new DrillRelOptUtil.RexFieldsTransformer(builder, corrMap);
for (RexNode expr : exprs) {
outputExprs.add(transformer.go(expr));
}
return outputExprs;
}
/**
* Given a of rexnode it transforms the rexnode by changing the expr to use new index mapped to the old index.
* @param builder : RexBuilder from the planner.
* @param expr: RexNode to be transformed.
* @param corrMap: Mapping between old index to new index.
* @return
*/
public static RexNode transformExpr(RexBuilder builder, RexNode expr, Map<Integer, Integer> corrMap) {
DrillRelOptUtil.RexFieldsTransformer transformer = new DrillRelOptUtil.RexFieldsTransformer(builder, corrMap);
return transformer.go(expr);
}
/**
* RexFieldsTransformer is a utility class used to convert column refs in a RexNode
* based on inputRefMap (input to output ref map).
*
* This transformer can be used to find and replace the existing inputRef in a RexNode with a new inputRef.
*/
public static class RexFieldsTransformer {
private final RexBuilder rexBuilder;
private final Map<Integer, Integer> inputRefMap;
public RexFieldsTransformer(
RexBuilder rexBuilder,
Map<Integer, Integer> inputRefMap) {
this.rexBuilder = rexBuilder;
this.inputRefMap = inputRefMap;
}
public RexNode go(RexNode rex) {
if (rex instanceof RexCall) {
ImmutableList.Builder<RexNode> builder = ImmutableList.builder();
final RexCall call = (RexCall) rex;
for (RexNode operand : call.operands) {
builder.add(go(operand));
}
return call.clone(call.getType(), builder.build());
} else if (rex instanceof RexInputRef) {
RexInputRef var = (RexInputRef) rex;
int index = var.getIndex();
return rexBuilder.makeInputRef(var.getType(), inputRefMap.get(index));
} else {
return rex;
}
}
}
public static boolean isProjectFlatten(RelNode project) {
assert project instanceof Project : "Rel is NOT an instance of project!";
for (RexNode rex : project.getChildExps()) {
RexNode newExpr = rex;
if (rex instanceof RexCall) {
RexCall function = (RexCall) rex;
String functionName = function.getOperator().getName();
if (functionName.equalsIgnoreCase("flatten") ) {
return true;
}
}
}
return false;
}
/**
* Stores information about fields, their names and types.
* Is responsible for creating mapper which used in field re-writer visitor.
*/
public static class ProjectPushInfo {
private final List<SchemaPath> fields;
private final FieldsReWriterUtil.FieldsReWriter reWriter;
private final List<String> fieldNames;
private final List<RelDataType> types;
public ProjectPushInfo(List<SchemaPath> fields, Map<String, FieldsReWriterUtil.DesiredField> desiredFields) {
this.fields = fields;
this.fieldNames = new ArrayList<>();
this.types = new ArrayList<>();
Map<RexNode, Integer> mapper = new HashMap<>();
int index = 0;
for (Map.Entry<String, FieldsReWriterUtil.DesiredField> entry : desiredFields.entrySet()) {
fieldNames.add(entry.getKey());
FieldsReWriterUtil.DesiredField desiredField = entry.getValue();
types.add(desiredField.getType());
for (RexNode node : desiredField.getNodes()) {
mapper.put(node, index);
}
index++;
}
this.reWriter = new FieldsReWriterUtil.FieldsReWriter(mapper);
}
public List<SchemaPath> getFields() {
return fields;
}
public FieldsReWriterUtil.FieldsReWriter getInputReWriter() {
return reWriter;
}
/**
* Creates new row type based on stores types and field names.
*
* @param factory factory for data type descriptors.
* @return new row type
*/
public RelDataType createNewRowType(RelDataTypeFactory factory) {
return factory.createStructType(types, fieldNames);
}
}
public static ProjectPushInfo getFieldsInformation(RelDataType rowType, List<RexNode> projects) {
ProjectFieldsVisitor fieldsVisitor = new ProjectFieldsVisitor(rowType);
for (RexNode exp : projects) {
PathSegment segment = exp.accept(fieldsVisitor);
fieldsVisitor.addField(segment);
}
return fieldsVisitor.getInfo();
}
/**
* Visitor that finds the set of inputs that are used.
*/
private static class ProjectFieldsVisitor extends RexVisitorImpl<PathSegment> {
private final List<String> fieldNames;
private final List<RelDataTypeField> fields;
private final Set<SchemaPath> newFields = Sets.newLinkedHashSet();
private final Map<String, FieldsReWriterUtil.DesiredField> desiredFields = new LinkedHashMap<>();
ProjectFieldsVisitor(RelDataType rowType) {
super(true);
this.fieldNames = rowType.getFieldNames();
this.fields = rowType.getFieldList();
}
void addField(PathSegment segment) {
if (segment instanceof PathSegment.NameSegment) {
newFields.add(new SchemaPath((PathSegment.NameSegment) segment));
}
}
ProjectPushInfo getInfo() {
return new ProjectPushInfo(ImmutableList.copyOf(newFields), ImmutableMap.copyOf(desiredFields));
}
@Override
public PathSegment visitInputRef(RexInputRef inputRef) {
int index = inputRef.getIndex();
String name = fieldNames.get(index);
RelDataTypeField field = fields.get(index);
addDesiredField(name, field.getType(), inputRef);
return new PathSegment.NameSegment(name);
}
@Override
public PathSegment visitCall(RexCall call) {
String itemStarFieldName = FieldsReWriterUtil.getFieldNameFromItemStarField(call, fieldNames);
if (itemStarFieldName != null) {
addDesiredField(itemStarFieldName, call.getType(), call);
return new PathSegment.NameSegment(itemStarFieldName);
}
if (SqlStdOperatorTable.ITEM.equals(call.getOperator())) {
PathSegment mapOrArray = call.operands.get(0).accept(this);
if (mapOrArray != null) {
if (call.operands.get(1) instanceof RexLiteral) {
return mapOrArray.cloneWithNewChild(Utilities.convertLiteral((RexLiteral) call.operands.get(1)));
}
return mapOrArray;
}
} else {
for (RexNode operand : call.operands) {
addField(operand.accept(this));
}
}
return null;
}
@Override
public PathSegment visitFieldAccess(RexFieldAccess fieldAccess) {
PathSegment refPath = fieldAccess.getReferenceExpr().accept(this);
PathSegment.NameSegment fieldPath = new PathSegment.NameSegment(fieldAccess.getField().getName());
return refPath.cloneWithNewChild(fieldPath);
}
private void addDesiredField(String name, RelDataType type, RexNode originalNode) {
FieldsReWriterUtil.DesiredField desiredField = desiredFields.get(name);
if (desiredField == null) {
desiredFields.put(name, new FieldsReWriterUtil.DesiredField(name, type, originalNode));
} else {
desiredField.addNode(originalNode);
}
}
}
/**
* Returns whether statistics-based estimates or guesses are used by the optimizer
* for the {@link RelNode} rel.
* @param rel : input
* @return TRUE if the estimate is a guess, FALSE otherwise
* */
public static boolean guessRows(RelNode rel) {
final PlannerSettings settings =
rel.getCluster().getPlanner().getContext().unwrap(PlannerSettings.class);
if (!settings.useStatistics()) {
return true;
}
/* We encounter RelSubset/HepRelVertex which are CALCITE constructs, hence we
* cannot add guessRows() to the DrillRelNode interface.
*/
if (rel instanceof RelSubset) {
if (((RelSubset) rel).getBest() != null) {
return guessRows(((RelSubset) rel).getBest());
} else if (((RelSubset) rel).getOriginal() != null) {
return guessRows(((RelSubset) rel).getOriginal());
}
} else if (rel instanceof HepRelVertex) {
if (((HepRelVertex) rel).getCurrentRel() != null) {
return guessRows(((HepRelVertex) rel).getCurrentRel());
}
} else if (rel instanceof TableScan) {
DrillTable table = Utilities.getDrillTable(rel.getTable());
try {
TableMetadata tableMetadata;
return table == null
|| (tableMetadata = table.getGroupScan().getTableMetadata()) == null
|| !TableStatisticsKind.HAS_DESCRIPTIVE_STATISTICS.getValue(tableMetadata);
} catch (IOException e) {
logger.debug("Unable to obtain table metadata due to exception: {}", e.getMessage(), e);
return true;
}
} else {
for (RelNode child : rel.getInputs()) {
if (guessRows(child)) { // at least one child is a guess
return true;
}
}
}
return false;
}
/**
* Returns whether the join condition is a simple equi-join or not. A simple equi-join is
* defined as an two-table equality join (no self-join)
* @param join : input join
* @param joinFieldOrdinals: join field ordinal w.r.t. the underlying inputs to the join
* @return TRUE if the join is a simple equi-join (not a self-join), FALSE otherwise
* */
public static boolean analyzeSimpleEquiJoin(Join join, int[] joinFieldOrdinals) {
RexNode joinExp = join.getCondition();
if (joinExp.getKind() != SqlKind.EQUALS) {
return false;
} else {
RexCall binaryExpression = (RexCall) joinExp;
RexNode leftComparand = binaryExpression.operands.get(0);
RexNode rightComparand = binaryExpression.operands.get(1);
if (!(leftComparand instanceof RexInputRef)) {
return false;
} else if (!(rightComparand instanceof RexInputRef)) {
return false;
} else {
int leftFieldCount = join.getLeft().getRowType().getFieldCount();
int rightFieldCount = join.getRight().getRowType().getFieldCount();
RexInputRef leftFieldAccess = (RexInputRef) leftComparand;
RexInputRef rightFieldAccess = (RexInputRef) rightComparand;
if (leftFieldAccess.getIndex() >= leftFieldCount + rightFieldCount ||
rightFieldAccess.getIndex() >= leftFieldCount + rightFieldCount) {
return false;
}
/* Both columns reference same table */
if ((leftFieldAccess.getIndex() >= leftFieldCount &&
rightFieldAccess.getIndex() >= leftFieldCount) ||
(leftFieldAccess.getIndex() < leftFieldCount &&
rightFieldAccess.getIndex() < leftFieldCount)) {
return false;
} else {
if (leftFieldAccess.getIndex() < leftFieldCount) {
joinFieldOrdinals[0] = leftFieldAccess.getIndex();
joinFieldOrdinals[1] = rightFieldAccess.getIndex() - leftFieldCount;
} else {
joinFieldOrdinals[0] = rightFieldAccess.getIndex();
joinFieldOrdinals[1] = leftFieldAccess.getIndex() - leftFieldCount;
}
return true;
}
}
}
}
public static DrillTable getDrillTable(final TableScan scan) {
DrillTable drillTable = null;
drillTable = scan.getTable().unwrap(DrillTable.class);
if (drillTable == null) {
DrillTranslatableTable transTable = scan.getTable().unwrap(DrillTranslatableTable.class);
if (transTable != null) {
drillTable = transTable.getDrillTable();
}
}
return drillTable;
}
public static List<Pair<Integer, Integer>> analyzeSimpleEquiJoin(Join join) {
List<Pair<Integer, Integer>> joinConditions = new ArrayList<>();
try {
RexVisitor<Void> visitor =
new RexVisitorImpl<Void>(true) {
public Void visitCall(RexCall call) {
if (call.getKind() == SqlKind.AND || call.getKind() == SqlKind.OR) {
super.visitCall(call);
} else {
if (call.getKind() == SqlKind.EQUALS) {
RexNode leftComparand = call.operands.get(0);
RexNode rightComparand = call.operands.get(1);
// If a join condition predicate has something more complicated than a RexInputRef
// we bail out!
if (!(leftComparand instanceof RexInputRef && rightComparand instanceof RexInputRef)) {
joinConditions.clear();
throw new Util.FoundOne(call);
}
int leftFieldCount = join.getLeft().getRowType().getFieldCount();
int rightFieldCount = join.getRight().getRowType().getFieldCount();
RexInputRef leftFieldAccess = (RexInputRef) leftComparand;
RexInputRef rightFieldAccess = (RexInputRef) rightComparand;
if (leftFieldAccess.getIndex() >= leftFieldCount + rightFieldCount ||
rightFieldAccess.getIndex() >= leftFieldCount + rightFieldCount) {
joinConditions.clear();
throw new Util.FoundOne(call);
}
/* Both columns reference same table */
if ((leftFieldAccess.getIndex() >= leftFieldCount &&
rightFieldAccess.getIndex() >= leftFieldCount) ||
(leftFieldAccess.getIndex() < leftFieldCount &&
rightFieldAccess.getIndex() < leftFieldCount)) {
joinConditions.clear();
throw new Util.FoundOne(call);
} else {
if (leftFieldAccess.getIndex() < leftFieldCount) {
joinConditions.add(Pair.of(leftFieldAccess.getIndex(),
rightFieldAccess.getIndex() - leftFieldCount));
} else {
joinConditions.add(Pair.of(rightFieldAccess.getIndex(),
leftFieldAccess.getIndex() - leftFieldCount));
}
}
}
}
return null;
}
};
join.getCondition().accept(visitor);
} catch (Util.FoundOne ex) {
Util.swallow(ex, null);
}
return joinConditions;
}
public static List<RexInputRef> findAllRexInputRefs(final RexNode node) {
List<RexInputRef> rexRefs = new ArrayList<>();
RexVisitor<Void> visitor =
new RexVisitorImpl<Void>(true) {
public Void visitInputRef(RexInputRef inputRef) {
rexRefs.add(inputRef);
return super.visitInputRef(inputRef);
}
};
node.accept(visitor);
return rexRefs;
}
}