/*
* 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.eigenbase.rel;
import java.util.*;
import org.eigenbase.rel.metadata.*;
import org.eigenbase.relopt.*;
import org.eigenbase.reltype.*;
import org.eigenbase.rex.*;
import org.eigenbase.sql.type.SqlTypeName;
import org.eigenbase.util.*;
import net.hydromatic.optiq.runtime.FlatLists;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
/**
* <code>JoinRelBase</code> is an abstract base class for implementations of
* {@link JoinRel}.
*/
public abstract class JoinRelBase extends AbstractRelNode {
//~ Instance fields --------------------------------------------------------
protected final RexNode condition;
protected RelNode left;
protected RelNode right;
protected final ImmutableSet<String> variablesStopped;
/**
* Values must be of enumeration {@link JoinRelType}, except that {@link
* JoinRelType#RIGHT} is disallowed.
*/
protected JoinRelType joinType;
//~ Constructors -----------------------------------------------------------
/**
* Creates a JoinRelBase.
*
* @param cluster Cluster
* @param traits Traits
* @param left Left input
* @param right Right input
* @param condition Join condition
* @param joinType Join type
* @param variablesStopped Set of names of variables which are set by the
* LHS and used by the RHS and are not available to
* nodes above this JoinRel in the tree
*/
protected JoinRelBase(
RelOptCluster cluster,
RelTraitSet traits,
RelNode left,
RelNode right,
RexNode condition,
JoinRelType joinType,
Set<String> variablesStopped) {
super(cluster, traits);
this.left = left;
this.right = right;
this.condition = condition;
this.variablesStopped = ImmutableSet.copyOf(variablesStopped);
assert joinType != null;
assert condition != null;
this.joinType = joinType;
}
//~ Methods ----------------------------------------------------------------
@Override
public List<RexNode> getChildExps() {
return ImmutableList.of(condition);
}
public RexNode getCondition() {
return condition;
}
public List<RelNode> getInputs() {
return FlatLists.of(left, right);
}
public JoinRelType getJoinType() {
return joinType;
}
public RelNode getLeft() {
return left;
}
public RelNode getRight() {
return right;
}
// TODO: enable
public boolean isValid_(boolean fail) {
if (!super.isValid(fail)) {
return false;
}
if (getRowType().getFieldCount()
!= getSystemFieldList().size()
+ left.getRowType().getFieldCount()
+ right.getRowType().getFieldCount()) {
assert !fail : "field count mismatch";
return false;
}
if (condition != null) {
if (condition.getType().getSqlTypeName() != SqlTypeName.BOOLEAN) {
assert !fail
: "condition must be boolean: " + condition.getType();
return false;
}
// The input to the condition is a row type consisting of system
// fields, left fields, and right fields. Very similar to the
// output row type, except that fields have not yet been made due
// due to outer joins.
RexChecker checker =
new RexChecker(
getCluster().getTypeFactory().builder()
.addAll(getSystemFieldList())
.addAll(getLeft().getRowType().getFieldList())
.addAll(getRight().getRowType().getFieldList())
.build(),
fail);
condition.accept(checker);
if (checker.getFailureCount() > 0) {
assert !fail
: checker.getFailureCount() + " failures in condition "
+ condition;
return false;
}
}
return true;
}
// implement RelNode
public RelOptCost computeSelfCost(RelOptPlanner planner) {
// REVIEW jvs 9-Apr-2006: Just for now...
double rowCount = RelMetadataQuery.getRowCount(this);
return planner.getCostFactory().makeCost(rowCount, 0, 0);
}
public static double estimateJoinedRows(
JoinRelBase joinRel,
RexNode condition) {
double product =
RelMetadataQuery.getRowCount(joinRel.getLeft())
* RelMetadataQuery.getRowCount(joinRel.getRight());
// TODO: correlation factor
return product * RelMetadataQuery.getSelectivity(joinRel, condition);
}
// implement RelNode
public double getRows() {
return estimateJoinedRows(this, condition);
}
public Set<String> getVariablesStopped() {
return variablesStopped;
}
public void childrenAccept(RelVisitor visitor) {
visitor.visit(left, 0, this);
visitor.visit(right, 1, this);
}
public RelWriter explainTerms(RelWriter pw) {
return super.explainTerms(pw)
.input("left", left)
.input("right", right)
.item("condition", condition)
.item("joinType", joinType.name().toLowerCase())
.itemIf(
"systemFields",
getSystemFieldList(),
!getSystemFieldList().isEmpty());
}
public void replaceInput(
int ordinalInParent,
RelNode p) {
switch (ordinalInParent) {
case 0:
this.left = p;
break;
case 1:
this.right = p;
break;
default:
throw Util.newInternal();
}
}
protected RelDataType deriveRowType() {
return deriveJoinRowType(
left.getRowType(),
right.getRowType(),
joinType,
getCluster().getTypeFactory(),
null,
getSystemFieldList());
}
/**
* Returns whether this JoinRel has already spawned a
* {@link org.eigenbase.rel.rules.SemiJoinRel} via
* {@link org.eigenbase.rel.rules.AddRedundantSemiJoinRule}.
*
* <p>The base implementation returns false.</p>
*
* @return whether this join has already spawned a semi join
*/
public boolean isSemiJoinDone() {
return false;
}
/**
* Returns a list of system fields that will be prefixed to
* output row type.
*
* @return list of system fields
*/
public List<RelDataTypeField> getSystemFieldList() {
return Collections.emptyList();
}
/**
* Derives the type of a join relational expression.
*
* @param leftType Row type of left input to join
* @param rightType Row type of right input to join
* @param joinType Type of join
* @param typeFactory Type factory
* @param fieldNameList List of names of fields; if null, field names are
* inherited and made unique
* @param systemFieldList List of system fields that will be prefixed to
* output row type; typically empty but must not be
* null
* @return join type
*/
public static RelDataType deriveJoinRowType(
RelDataType leftType,
RelDataType rightType,
JoinRelType joinType,
RelDataTypeFactory typeFactory,
List<String> fieldNameList,
List<RelDataTypeField> systemFieldList) {
assert systemFieldList != null;
switch (joinType) {
case LEFT:
rightType = typeFactory.createTypeWithNullability(rightType, true);
break;
case RIGHT:
leftType = typeFactory.createTypeWithNullability(leftType, true);
break;
case FULL:
leftType = typeFactory.createTypeWithNullability(leftType, true);
rightType = typeFactory.createTypeWithNullability(rightType, true);
break;
default:
break;
}
return createJoinType(
typeFactory, leftType, rightType, fieldNameList, systemFieldList);
}
/**
* Returns the type the row which results when two relations are joined.
*
* <p>The resulting row type consists of
* the system fields (if any), followed by
* the fields of the left type, followed by
* the fields of the right type. The field name list, if present, overrides
* the original names of the fields.
*
* @param typeFactory Type factory
* @param leftType Type of left input to join
* @param rightType Type of right input to join
* @param fieldNameList If not null, overrides the original names of the
* fields
* @param systemFieldList List of system fields that will be prefixed to
* output row type; typically empty but must not be
* null
* @return type of row which results when two relations are joined
*/
public static RelDataType createJoinType(
RelDataTypeFactory typeFactory,
RelDataType leftType,
RelDataType rightType,
List<String> fieldNameList,
List<RelDataTypeField> systemFieldList) {
assert (fieldNameList == null)
|| (fieldNameList.size()
== (systemFieldList.size()
+ leftType.getFieldCount()
+ rightType.getFieldCount()));
List<String> nameList = new ArrayList<String>();
List<RelDataType> typeList = new ArrayList<RelDataType>();
// use a hashset to keep track of the field names; this is needed
// to ensure that the contains() call to check for name uniqueness
// runs in constant time; otherwise, if the number of fields is large,
// doing a contains() on a list can be expensive
HashSet<String> uniqueNameList = new HashSet<String>();
addFields(systemFieldList, typeList, nameList, uniqueNameList);
addFields(leftType.getFieldList(), typeList, nameList, uniqueNameList);
if (rightType != null) {
addFields(
rightType.getFieldList(), typeList, nameList, uniqueNameList);
}
if (fieldNameList != null) {
assert fieldNameList.size() == nameList.size();
nameList = fieldNameList;
}
return typeFactory.createStructType(typeList, nameList);
}
private static void addFields(
List<RelDataTypeField> fieldList,
List<RelDataType> typeList,
List<String> nameList,
HashSet<String> uniqueNameList) {
for (RelDataTypeField field : fieldList) {
String name = field.getName();
// Ensure that name is unique from all previous field names
if (uniqueNameList.contains(name)) {
String nameBase = name;
for (int j = 0;; j++) {
name = nameBase + j;
if (!uniqueNameList.contains(name)) {
break;
}
}
}
nameList.add(name);
uniqueNameList.add(name);
typeList.add(field.getType());
}
}
@Override
public final JoinRelBase copy(RelTraitSet traitSet, List<RelNode> inputs) {
assert inputs.size() == 2;
return copy(traitSet, getCondition(), inputs.get(0), inputs.get(1),
joinType, isSemiJoinDone());
}
/**
* Creates a copy of this join, overriding condition, system fields and
* inputs.
*
* <p>General contract as {@link org.eigenbase.rel.RelNode#copy}.
*
* @param conditionExpr Condition
* @param left Left input
* @param right Right input
* @param joinType Join type
* @param semiJoinDone Whether this join has been translated to a
* semi-join
* @return Copy of this join
*/
public abstract JoinRelBase copy(RelTraitSet traitSet, RexNode conditionExpr,
RelNode left, RelNode right, JoinRelType joinType, boolean semiJoinDone);
/** Analyzes the join condition. */
public JoinInfo analyzeCondition() {
return JoinInfo.of(left, right, condition);
}
}
// End JoinRelBase.java