/*
* 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.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.teiid.api.exception.query.QueryProcessingException;
import org.teiid.client.plan.PlanNode;
import org.teiid.common.buffer.BlockedException;
import org.teiid.common.buffer.BufferManager.TupleSourceType;
import org.teiid.common.buffer.TupleBatch;
import org.teiid.common.buffer.TupleBuffer;
import org.teiid.common.buffer.TupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.QueryPlugin;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.processor.RegisterRequestParameter;
import org.teiid.query.sql.lang.BatchedUpdateCommand;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.Insert;
import org.teiid.query.sql.lang.SourceHint;
import org.teiid.query.sql.symbol.Constant;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.GroupSymbol;
import org.teiid.translator.ExecutionFactory.TransactionSupport;
public class ProjectIntoNode extends RelationalNode {
public enum Mode {
BATCH, ITERATOR, SINGLE
}
private static int REQUEST_CREATION = 1;
private static int RESPONSE_PROCESSING = 2;
// Initialization state
private GroupSymbol intoGroup;
private List intoElements;
private String modelName;
private Mode mode;
private boolean upsert;
// Processing state
private long batchRow = 1;
private long insertCount = 0;
private int phase = REQUEST_CREATION;
private int requestsRegistered = 0;
private int tupleSourcesProcessed = 0;
private boolean sourceDone;
private TupleBuffer buffer;
private TupleBuffer last;
private TupleBatch currentBatch;
private TupleSource tupleSource;
private Criteria constraint;
private Evaluator eval;
private TransactionSupport transactionSupport;
private SourceHint sourceHint;
protected ProjectIntoNode() {
super();
}
public ProjectIntoNode(int nodeID) {
super(nodeID);
}
public void reset() {
super.reset();
this.phase = REQUEST_CREATION;
this.batchRow = 1;
this.insertCount = 0;
this.tupleSourcesProcessed = 0;
this.requestsRegistered = 0;
this.currentBatch=null;
this.sourceDone=false;
}
public void setIntoGroup(GroupSymbol group) {
this.intoGroup = group;
}
public void setIntoElements(List intoElements) {
this.intoElements = intoElements;
}
public void setModelName(String modelName) {
this.modelName = modelName;
}
/**
* Get batch from child node
* Walk through each row of child batch
* Bind values to insertCommand
* Execute insertCommand
* Update insertCount
* When no more data is available, output batch with single row containing insertCount
*/
public TupleBatch nextBatchDirect()
throws BlockedException, TeiidComponentException, TeiidProcessingException {
while(phase == REQUEST_CREATION) {
/* If we don't have a batch to work, get the next
*/
if (currentBatch == null) {
if (sourceDone) {
phase = RESPONSE_PROCESSING;
break;
}
currentBatch = getChildren()[0].nextBatch(); // can throw BlockedException
sourceDone = currentBatch.getTerminationFlag();
this.batchRow = currentBatch.getBeginRow();
//normally we would want to skip a 0 sized batch, but it typically represents the terminal batch
//and for implicit temp tables we need to issue an empty insert
if(currentBatch.getRowCount() == 0
&& (!currentBatch.getTerminationFlag() || mode != Mode.ITERATOR)) {
currentBatch = null;
continue;
}
if (this.constraint != null) {
//row based security check
if (eval == null) {
eval = new Evaluator(createLookupMap(this.intoElements), this.getDataManager(), getContext());
}
List<List<?>> tuples = this.currentBatch.getTuples();
for (int i = 0; i < tuples.size(); i++) {
if (!eval.evaluate(constraint, tuples.get(i))) {
throw new QueryProcessingException(QueryPlugin.Event.TEIID31130, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31130, new Insert(intoGroup, this.intoElements, convertValuesToConstants(tuples.get(i), intoElements))));
}
}
}
}
if (mode != Mode.ITERATOR) { //delay the check in the iterator case to accumulate batches
checkExitConditions();
}
int batchSize = currentBatch.getRowCount();
int requests = 1;
switch (mode) {
case ITERATOR:
if (buffer == null) {
buffer = getBufferManager().createTupleBuffer(intoElements, getConnectionID(), TupleSourceType.PROCESSOR);
}
if (sourceDone) {
//if there is a pending request we can't process the last until it is done
checkExitConditions();
}
for (List<?> tuple : currentBatch.getTuples()) {
buffer.addTuple(tuple);
}
try {
checkExitConditions();
} catch (BlockedException e) {
//move to the next batch
this.batchRow += batchSize;
currentBatch = null;
continue;
}
if (currentBatch.getTerminationFlag() && (buffer.getRowCount() != 0 || intoGroup.isImplicitTempGroupSymbol())) {
registerIteratorRequest();
} else if (buffer.getRowCount() >= buffer.getBatchSize() * 4) {
registerIteratorRequest();
} else {
requests = 0;
}
break;
case BATCH:
// Register batched update command against source
long endRow = currentBatch.getEndRow();
List<Command> rows = new ArrayList<Command>((int)(endRow-batchRow));
for(long rowNum = batchRow; rowNum <= endRow; rowNum++) {
Insert insert = new Insert( intoGroup,
intoElements,
convertValuesToConstants(currentBatch.getTuple(rowNum), intoElements));
insert.setSourceHint(sourceHint);
insert.setUpsert(upsert);
rows.add( insert );
}
registerRequest(new BatchedUpdateCommand( rows ));
break;
case SINGLE:
batchSize = 1;
// Register insert command against source
// Defect 16036 - submit a new INSERT command to the DataManager.
Insert insert = new Insert(intoGroup, intoElements, convertValuesToConstants(currentBatch.getTuple(batchRow), intoElements));
insert.setSourceHint(sourceHint);
insert.setUpsert(upsert);
registerRequest(insert);
}
this.batchRow += batchSize;
if (batchRow > currentBatch.getEndRow()) {
currentBatch = null;
}
this.requestsRegistered+=requests;
}
checkExitConditions();
if (this.buffer != null) {
this.buffer.remove();
this.buffer = null;
}
// End this node's work
//report only a max int
int count = (int)Math.min(Integer.MAX_VALUE, insertCount);
addBatchRow(Arrays.asList(count));
terminateBatches();
return pullBatch();
}
private void registerIteratorRequest() throws TeiidComponentException,
TeiidProcessingException {
Insert insert = new Insert(intoGroup, intoElements, null);
insert.setSourceHint(sourceHint);
insert.setUpsert(upsert);
buffer.close();
insert.setTupleSource(buffer.createIndexedTupleSource(true));
// Register insert command against source
registerRequest(insert);
//remove the old buffer when the insert is complete
last = buffer;
buffer = null;
}
private void checkExitConditions() throws TeiidComponentException, BlockedException, TeiidProcessingException {
if (tupleSource != null) {
if (mode == Mode.BATCH || mode == Mode.ITERATOR) {
List<?> tuple = null;
while ((tuple = tupleSource.nextTuple()) != null) {
Integer count = (Integer)tuple.get(0);
if (count > 0 || count == Statement.SUCCESS_NO_INFO) {
insertCount++;
}
}
} else {
Integer count = (Integer)tupleSource.nextTuple().get(0);
insertCount += count.intValue();
}
closeRequest();
// Mark as processed
tupleSourcesProcessed++; // This should set tupleSourcesProcessed to be the same as requestsRegistered
}
// RESPONSE_PROCESSING: process tuple sources
if (tupleSourcesProcessed < requestsRegistered) {
throw BlockedException.block(getContext().getRequestId(), "Blocking on insert update count"); //$NON-NLS-1$
}
}
private void registerRequest(Command command) throws TeiidComponentException, TeiidProcessingException {
tupleSource = getDataManager().registerRequest(getContext(), command, this.modelName, new RegisterRequestParameter(null, getID(), -1));
}
private void closeRequest() {
if (this.last != null) {
this.last.remove();
this.last = null;
}
if (this.tupleSource != null) {
tupleSource.closeSource();
this.tupleSource = null;
}
}
protected void getNodeString(StringBuffer str) {
super.getNodeString(str);
str.append(intoGroup);
}
public Object clone(){
ProjectIntoNode clonedNode = new ProjectIntoNode();
super.copyTo(clonedNode);
clonedNode.intoGroup = intoGroup;
clonedNode.intoElements = intoElements;
clonedNode.modelName = this.modelName;
clonedNode.mode = this.mode;
clonedNode.constraint = this.constraint;
clonedNode.sourceHint = this.sourceHint;
clonedNode.upsert = this.upsert;
return clonedNode;
}
public PlanNode getDescriptionProperties() {
PlanNode props = super.getDescriptionProperties();
props.addProperty(PROP_INTO_GROUP, intoGroup.toString());
if (upsert) {
props.addProperty(PROP_UPSERT, "true"); //$NON-NLS-1$
}
List<String> selectCols = new ArrayList<String>(intoElements.size());
for(int i=0; i<this.intoElements.size(); i++) {
selectCols.add(this.intoElements.get(i).toString());
}
props.addProperty(PROP_SELECT_COLS, selectCols);
return props;
}
private List<Constant> convertValuesToConstants(List<?> values, List<ElementSymbol> elements) {
ArrayList<Constant> constants = new ArrayList<Constant>(values.size());
for(int i=0; i<elements.size(); i++) {
ElementSymbol es = elements.get(i);
Class<?> type = es.getType();
constants.add(new Constant(values.get(i),type));
}
return constants;
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public boolean isTempGroupInsert() {
return intoGroup.isTempGroupSymbol();
}
public void closeDirect() {
if (this.buffer != null) {
this.buffer.remove();
this.buffer = null;
}
closeRequest();
}
public String getModelName() {
return modelName;
}
@Override
public Boolean requiresTransaction(boolean transactionalReads) {
Boolean requires = this.getChildren()[0].requiresTransaction(transactionalReads);
if (requires != null && requires) {
return true;
}
if (transactionSupport == TransactionSupport.NONE) {
return requires;
}
return true;
}
public void setConstraint(Criteria constraint) {
this.constraint = constraint;
}
public void setTransactionSupport(TransactionSupport transactionSupport) {
this.transactionSupport = transactionSupport;
}
public void setSourceHint(SourceHint property) {
this.sourceHint = property;
}
public void setUpsert(boolean upsert) {
this.upsert = upsert;
}
}