/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.query.processor.relational;
import static org.teiid.query.analysis.AnalysisRecord.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.teiid.api.exception.query.ExpressionEvaluationException;
import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.TupleBuffer;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.processor.ProcessorDataManager;
import org.teiid.query.processor.relational.SourceState.ImplicitBuffer;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.JoinType;
import org.teiid.query.util.CommandContext;
/**
* @since 4.2
*/
public class JoinNode extends SubqueryAwareRelationalNode {
static class BatchAvailableException extends RuntimeException {}
static BatchAvailableException BATCH_AVILABLE = new BatchAvailableException();
public enum JoinStrategyType {
MERGE,
ENHANCED_SORT,
NESTED_LOOP,
NESTED_TABLE
}
private enum State { LOAD_LEFT, LOAD_RIGHT, EXECUTE }
private State state = State.LOAD_LEFT;
private JoinStrategy joinStrategy;
private JoinType joinType;
private String dependentValueSource;
private List leftExpressions;
private List rightExpressions;
private boolean leftDistinct;
private boolean rightDistinct;
private Criteria joinCriteria;
private Map combinedElementMap;
private int[] projectionIndexes;
private DependentValueSource dvs;
public JoinNode(int nodeID) {
super(nodeID);
}
public void setJoinType(JoinType type) {
this.joinType = type;
}
public JoinStrategy getJoinStrategy() {
return this.joinStrategy;
}
public void setJoinStrategy(JoinStrategy joinStrategy) {
this.joinStrategy = joinStrategy;
}
public void setJoinExpressions(List leftExpressions, List rightExpressions) {
this.leftExpressions = leftExpressions;
this.rightExpressions = rightExpressions;
}
public boolean isLeftDistinct() {
return leftDistinct;
}
public void setLeftDistinct(boolean leftDistinct) {
this.leftDistinct = leftDistinct;
}
public boolean isRightDistinct() {
return rightDistinct;
}
public void setRightDistinct(boolean rightDistinct) {
this.rightDistinct = rightDistinct;
}
public void setJoinCriteria(Criteria joinCriteria) {
this.joinCriteria = joinCriteria;
}
@Override
public void initialize(CommandContext context, BufferManager bufferManager,
ProcessorDataManager dataMgr) {
super.initialize(context, bufferManager, dataMgr);
if (this.combinedElementMap == null) {
// Create element lookup map for evaluating project expressions
List combinedElements = new ArrayList(getChildren()[0].getElements());
combinedElements.addAll(getChildren()[1].getElements());
this.combinedElementMap = createLookupMap(combinedElements);
this.projectionIndexes = getProjectionIndexes(combinedElementMap, getElements());
}
}
public void open()
throws TeiidComponentException, TeiidProcessingException {
// Set Up Join Strategy
this.joinStrategy.initialize(this);
if (isDependent() && (this.joinType == JoinType.JOIN_ANTI_SEMI || this.joinType == JoinType.JOIN_SEMI)) {
joinStrategy.openRight();
} else {
joinStrategy.openLeft();
}
if(!isDependent()) {
joinStrategy.openRight();
}
this.state = State.LOAD_LEFT;
}
/**
* @see org.teiid.query.processor.relational.RelationalNode#clone()
* @since 4.2
*/
public Object clone() {
JoinNode clonedNode = new JoinNode(super.getID());
super.copyTo(clonedNode);
clonedNode.joinType = this.joinType;
clonedNode.joinStrategy = this.joinStrategy.clone();
clonedNode.joinCriteria = this.joinCriteria;
clonedNode.leftExpressions = leftExpressions;
clonedNode.rightExpressions = rightExpressions;
clonedNode.dependentValueSource = this.dependentValueSource;
clonedNode.rightDistinct = rightDistinct;
clonedNode.leftDistinct = leftDistinct;
return clonedNode;
}
/**
* @see org.teiid.query.processor.relational.RelationalNode#nextBatchDirect()
* @since 4.2
*/
protected TupleBatch nextBatchDirect() throws BlockedException,
TeiidComponentException,
TeiidProcessingException {
try {
if (state == State.LOAD_LEFT) {
boolean rightDep = false;
//dependent semi joins are processed right first
if (isDependent() && (this.joinType == JoinType.JOIN_ANTI_SEMI || this.joinType == JoinType.JOIN_SEMI)) {
rightDep = true;
this.joinStrategy.openRight();
this.joinStrategy.loadRight();
TupleBuffer buffer = this.joinStrategy.rightSource.getTupleBuffer();
//the tuplebuffer may be from a lower node, so pass in the schema
dvs = new DependentValueSource(buffer, this.joinStrategy.rightSource.getSource().getElements());
dvs.setDistinct(this.joinStrategy.rightSource.isExpresssionDistinct());
this.getContext().getVariableContext().setGlobalValue(this.dependentValueSource, dvs);
}
if (this.joinType != JoinType.JOIN_FULL_OUTER || this.getJoinCriteria() == null) {
this.joinStrategy.leftSource.setImplicitBuffer(ImplicitBuffer.NONE);
}
this.joinStrategy.openLeft();
this.joinStrategy.loadLeft();
if (isDependent() && !rightDep) {
TupleBuffer buffer = this.joinStrategy.leftSource.getTupleBuffer();
//the tuplebuffer may be from a lower node, so pass in the schema
dvs = new DependentValueSource(buffer, this.joinStrategy.leftSource.getSource().getElements());
dvs.setDistinct(this.joinStrategy.leftSource.isExpresssionDistinct());
this.getContext().getVariableContext().setGlobalValue(this.dependentValueSource, dvs);
}
state = State.LOAD_RIGHT;
}
} catch (BlockedException e) {
if (!isDependent()) {
if (getJoinType() != JoinType.JOIN_FULL_OUTER && this.joinStrategy.leftSource.getSortUtility() == null && this.joinStrategy.leftSource.rowCountLE(0)) {
this.terminateBatches();
return pullBatch();
}
this.joinStrategy.openRight();
this.joinStrategy.loadRight();
prefetch(this.joinStrategy.rightSource, this.joinStrategy.leftSource);
}
throw e;
}
try {
if (state == State.LOAD_RIGHT) {
if (getJoinType() != JoinType.JOIN_FULL_OUTER && this.joinStrategy.leftSource.getSortUtility() == null && this.joinStrategy.leftSource.rowCountLE(0)) {
this.terminateBatches();
return pullBatch();
}
this.joinStrategy.openRight();
this.joinStrategy.loadRight();
state = State.EXECUTE;
}
this.joinStrategy.process();
this.terminateBatches();
} catch (BatchAvailableException e) {
//pull the batch
} catch (BlockedException e) {
//TODO: this leads to duplicate exceptions, we
//could track which side is blocking
try {
prefetch(this.joinStrategy.leftSource, this.joinStrategy.rightSource);
} catch (BlockedException e1) {
}
prefetch(this.joinStrategy.rightSource, this.joinStrategy.leftSource);
throw e;
}
return pullBatch();
}
private void prefetch(SourceState toFetch, SourceState other) throws TeiidComponentException,
TeiidProcessingException {
toFetch.prefetch(Math.max(1l, other.getIncrementalRowCount(false)/other.getSource().getBatchSize())*toFetch.getSource().getBatchSize());
}
/**
* @see org.teiid.query.processor.relational.RelationalNode#getDescriptionProperties()
* @since 4.2
*/
public PlanNode getDescriptionProperties() {
// Default implementation - should be overridden
PlanNode props = super.getDescriptionProperties();
if(isDependent()) {
props.addProperty(PROP_DEPENDENT, Boolean.TRUE.toString());
}
props.addProperty(PROP_JOIN_STRATEGY, this.joinStrategy.toString());
props.addProperty(PROP_JOIN_TYPE, this.joinType.toString());
List<String> critList = getCriteriaList();
props.addProperty(PROP_JOIN_CRITERIA, critList);
return props;
}
private List<String> getCriteriaList() {
List<String> critList = new ArrayList<String>();
if (leftExpressions != null) {
for(int i=0; i < this.leftExpressions.size(); i++) {
critList.add(this.leftExpressions.get(i).toString() + "=" + this.rightExpressions.get(i).toString()); //$NON-NLS-1$
}
}
if (this.joinCriteria != null) {
for (Criteria crit : (List<Criteria>)Criteria.separateCriteriaByAnd(joinCriteria)) {
critList.add(crit.toString());
}
}
return critList;
}
/**
* @see org.teiid.query.processor.relational.RelationalNode#getNodeString(java.lang.StringBuffer)
* @since 4.2
*/
protected void getNodeString(StringBuffer str) {
str.append(getClassName());
str.append("("); //$NON-NLS-1$
str.append(getID());
str.append(") [");//$NON-NLS-1$
if(isDependent()) {
str.append("Dependent] [");//$NON-NLS-1$
}
str.append(this.joinStrategy.toString());
str.append("] [");//$NON-NLS-1$
str.append(this.joinType.toString());
str.append("]"); //$NON-NLS-1$
if (getJoinType() != JoinType.JOIN_CROSS) {
str.append(" criteria=").append(getCriteriaList()); //$NON-NLS-1$
}
str.append(" output="); //$NON-NLS-1$
str.append(getElements());
}
/**
* @return Returns the isDependent.
*/
public boolean isDependent() {
return this.dependentValueSource != null;
}
/**
* @param isDependent The isDependent to set.
*/
public void setDependentValueSource(String dependentValueSource) {
this.dependentValueSource = dependentValueSource;
}
public String getDependentValueSourceName() {
return dependentValueSource;
}
public void closeDirect() {
super.closeDirect();
joinStrategy.close();
if (this.getContext() != null && this.dependentValueSource != null) {
this.getContext().getVariableContext().setGlobalValue(this.dependentValueSource, null);
}
this.dvs = null;
}
@Override
public void reset() {
super.reset();
this.joinStrategy = this.joinStrategy.clone();
this.dvs = null;
}
public JoinType getJoinType() {
return this.joinType;
}
Map getCombinedElementMap() {
return this.combinedElementMap;
}
public Criteria getJoinCriteria() {
return this.joinCriteria;
}
boolean matchesCriteria(List outputTuple) throws BlockedException, TeiidComponentException, ExpressionEvaluationException {
return (this.joinCriteria == null || getEvaluator(this.combinedElementMap).evaluate(this.joinCriteria, outputTuple));
}
public List getLeftExpressions() {
return this.leftExpressions;
}
public List getRightExpressions() {
return this.rightExpressions;
}
@Override
protected void addBatchRow(List row) {
List projectTuple = projectTuple(this.projectionIndexes, row);
super.addBatchRow(projectTuple);
if (isBatchFull()) {
throw BATCH_AVILABLE;
}
}
public DependentValueSource getDependentValueSource() {
return dvs;
}
@Override
public Collection<? extends LanguageObject> getObjects() {
List<LanguageObject> all = new ArrayList<LanguageObject>();
if (leftExpressions != null) {
all.addAll(leftExpressions);
all.addAll(rightExpressions);
}
if (joinCriteria != null) {
all.add(joinCriteria);
}
return all;
}
}