/*
* 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 java.util.Collections;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager;
import org.teiid.common.buffer.BufferManager.BufferReserveMode;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.impl.BufferManagerImpl;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.processor.ProcessorDataManager;
import org.teiid.query.util.CommandContext;
public class UnionAllNode extends RelationalNode {
private static final int SMALL_LIMIT = 10;
private boolean[] sourceDone;
private boolean[] sourceOpen;
private int outputRow = 1;
private int reserved;
private int schemaSize;
public UnionAllNode(int nodeID) {
super(nodeID);
}
public void reset() {
super.reset();
sourceDone = null;
sourceOpen = null;
outputRow = 1;
}
@Override
public void initialize(CommandContext context, BufferManager bufferManager,
ProcessorDataManager dataMgr) {
super.initialize(context, bufferManager, dataMgr);
this.schemaSize = getBufferManager().getSchemaSize(getOutputElements());
}
public void open()
throws TeiidComponentException, TeiidProcessingException {
// Initialize done flags
sourceDone = new boolean[getChildren().length];
// Detect if we should be more conservative than the default strategy of opening all children
RelationalNode parent = this.getParent();
int rowLimit = -1;
while (parent != null) {
if (parent instanceof LimitNode) {
LimitNode limit = (LimitNode)parent;
rowLimit = limit.getLimit();
if (rowLimit != -1 && limit.getOffset() > 0) {
rowLimit += limit.getOffset();
}
break;
} else if (parent instanceof SortNode) {
break;
} else if (!(parent instanceof SelectNode) || !(parent instanceof ProjectNode) || !(parent instanceof UnionAllNode)) {
break;
}
parent = parent.getParent();
}
if (rowLimit != -1 && rowLimit < Integer.MAX_VALUE) {
int toOpen = 2;
if (rowLimit > SMALL_LIMIT) {
if (rowLimit < BufferManagerImpl.DEFAULT_PROCESSOR_BATCH_SIZE) {
toOpen = Math.max(Math.min(this.getChildCount(), this.getContext().getUserRequestSourceConcurrency())/3, 2);
} else {
Math.max(2, this.getChildCount()/2 + this.getChildCount()%2);
}
}
//we use the 2x multiple here because the default strategy can proactively execute unneeded results
if (toOpen < this.getContext().getUserRequestSourceConcurrency()*2) {
if (reserved == 0) {
reserved = getBufferManager().reserveBuffers((toOpen) * schemaSize, BufferReserveMode.FORCE);
}
sourceOpen = new boolean[this.getChildCount()];
//we want to be selective about the number of children we open
//ideally we would
RelationalNode[] children = this.getChildren();
for(int i=0; i<toOpen; i++) {
children[i].open();
sourceOpen[i] = true;
}
return;
}
}
if (reserved == 0) {
reserved = getBufferManager().reserveBuffers((getChildCount()) * schemaSize, BufferReserveMode.FORCE);
}
// Open the children
super.open();
}
public TupleBatch nextBatchDirect()
throws BlockedException, TeiidComponentException, TeiidProcessingException {
// Walk through all children and for each one that isn't done, try to retrieve a batch
// When all sources are done, set the termination flag on that batch
RelationalNode[] children = getChildren();
int childCount = getChildCount();
int activeSources = 0;
TupleBatch batch = null;
boolean additionalSources = false;
for(int i=0; i<childCount; i++) {
if(children[i] != null && ! sourceDone[i]) {
if (sourceOpen != null && !sourceOpen[i]) {
additionalSources = true;
continue;
}
activeSources++;
if(batch == null) {
try {
batch = children[i].nextBatch();
// Got a batch
if(batch.getTerminationFlag() == true) {
// Mark source as being done and decrement the activeSources counter
sourceDone[i] = true;
activeSources--;
if (reserved > 0) {
getBufferManager().releaseBuffers(schemaSize);
reserved-=schemaSize;
}
}
} catch(BlockedException e) {
// no problem - try the next one
}
} else {
// We already have a batch, so we know that
// 1) we have a batch to return and
// 2) this isn't the last active source, so we're not returning the last batch
// This is sufficient to break the loop - we won't learn anything new after this
break;
}
}
}
// Determine what to return
TupleBatch outputBatch = null;
if(batch != null) {
// Rebuild the batch to reset the output row
outputBatch = new TupleBatch(outputRow, batch.getTuples());
// This is the last unioned batch if:
// 1) This batch is a termination batch from the child
// 2) No other active sources exist
outputBatch.setTerminationFlag(batch.getTerminationFlag() && activeSources == 0 && !additionalSources);
// Update output row for next batch
outputRow += outputBatch.getRowCount();
} else if(activeSources > 0) {
// Didn't get a batch but there are active sources so we are blocked
throw BlockedException.block(getContext().getRequestId(), "Blocking on union source.", getID()); //$NON-NLS-1$
} else {
boolean openedAny = false;
int toOpen = 0;
if (sourceOpen != null) {
for(int i=0; i<childCount; i++) {
if(sourceOpen[i] && sourceDone[i]) {
toOpen++;
}
}
for(int i=0; i<childCount && toOpen > 0; i++) {
if(!sourceOpen[i]) {
getBufferManager().reserveBuffers(schemaSize, BufferReserveMode.FORCE);
reserved+=schemaSize;
children[i].open();
sourceOpen[i] = true;
openedAny = true;
toOpen--;
}
}
}
if (openedAny) {
return nextBatchDirect();
}
// No batch and no active sources - return empty termination batch (should never happen but just in case)
outputBatch = new TupleBatch(outputRow, Collections.EMPTY_LIST);
outputBatch.setTerminationFlag(true);
}
return outputBatch;
}
@Override
public void closeDirect() {
if (reserved > 0) {
getBufferManager().releaseBuffers(reserved);
reserved = 0;
}
}
public Object clone(){
UnionAllNode clonedNode = new UnionAllNode(super.getID());
super.copyTo(clonedNode);
return clonedNode;
}
}