/*
* 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;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.query.QueryPlugin;
import org.teiid.query.sql.lang.Command;
import org.teiid.query.sql.util.VariableContext;
import org.teiid.query.util.CommandContext;
import org.teiid.translator.TranslatorBatchException;
/**
* Plan for execution for a batched update command. The plan executes the child plans of the
* individual commands in order.
*
* If variableContexts are provided, then this is a bulk update where all plans are the same object.
*
* @since 4.2
*/
public class BatchedUpdatePlan extends ProcessorPlan {
/** Array that holds the child update plans */
private ProcessorPlan[] updatePlans;
/** */
private boolean[] planOpened;
/** Array that holds the update counts for each command in this batch */
private List[] updateCounts;
/** The position of the plan currently being executed */
private int planIndex = 0;
/** The position of the command for which the update count is being retrieved */
private int commandIndex = 0;
private List<VariableContext> contexts; //only set for bulk updates
private boolean singleResult;
/**
*
* @param childPlans the child update plans for this batch
* @param commandsInBatch The total number of commands in this batch. This does not always equal the number of plans if some
* commands have been batched together.
* @param singleResult indicates only a single update count is expected - non-single result plans are required to be top level
* @since 4.2
*/
public BatchedUpdatePlan(List<? extends ProcessorPlan> childPlans, int commandsInBatch, List<VariableContext> contexts, boolean singleResult) {
this.updatePlans = childPlans.toArray(new ProcessorPlan[childPlans.size()]);
this.planOpened = new boolean[updatePlans.length];
this.updateCounts = new List[commandsInBatch];
this.contexts = contexts;
this.singleResult = singleResult;
}
/**
* @see java.lang.Object#clone()
* @since 4.2
*/
public BatchedUpdatePlan clone() {
List<ProcessorPlan> clonedPlans = new ArrayList<ProcessorPlan>(updatePlans.length);
clonedPlans.add(updatePlans[0].clone());
for (int i = 1; i <updatePlans.length; i++) {
if (contexts == null) {
clonedPlans.add(updatePlans[1].clone());
} else {
clonedPlans.add(clonedPlans.get(0));
}
}
BatchedUpdatePlan clone = new BatchedUpdatePlan(clonedPlans, updateCounts.length, contexts, singleResult);
return clone;
}
/**
* @see org.teiid.query.processor.ProcessorPlan#initialize(org.teiid.query.util.CommandContext, org.teiid.query.processor.ProcessorDataManager, org.teiid.common.buffer.BufferManager)
* @since 4.2
*/
public void initialize(CommandContext context, ProcessorDataManager dataMgr, BufferManager bufferMgr) {
context = context.clone();
context.setVariableContext(new VariableContext()); //start a new root variable context
this.setContext(context);
// Initialize all the child plans
for (int i = 0; i < getPlanCount(); i++) {
updatePlans[i].initialize(context, dataMgr, bufferMgr);
}
}
/**
* @see org.teiid.query.processor.ProcessorPlan#getOutputElements()
* @since 4.2
*/
public List getOutputElements() {
return Command.getUpdateCommandSymbol();
}
/**
* @see org.teiid.query.processor.ProcessorPlan#open()
* @since 4.2
*/
public void open() throws TeiidComponentException, TeiidProcessingException {
try {
// It's ok to open() the first plan, as it is not dependent on any prior commands.
// See note for defect 16166 in the nextBatch() method.
openPlan();
} catch (BlockedException e){
//should not happen
throw e;
} catch (TeiidComponentException | TeiidProcessingException e) {
if (singleResult) {
throw e;
}
Throwable cause = e;
if (e.getCause() instanceof TranslatorBatchException) {
TranslatorBatchException tbe = (TranslatorBatchException)e.getCause();
for (int i = 0; i < tbe.getUpdateCounts().length; i++) {
updateCounts[commandIndex++] = Arrays.asList(updateCounts[i]);
}
}
updateCounts = Arrays.copyOf(updateCounts, commandIndex);
getContext().setBatchUpdateException(cause);
}
}
/**
* @see org.teiid.query.processor.ProcessorPlan#nextBatch()
* @since 4.2
*/
public TupleBatch nextBatch() throws BlockedException, TeiidComponentException, TeiidProcessingException {
for (;planIndex < updatePlans.length && (getContext() == null || getContext().getBatchUpdateException() == null);) {
try {
if (!planOpened[planIndex]) { // Open the plan only once
/* Defect 16166
* Some commands in a batch may depend on updates by previous commands in the same batch. A call
* to open() usually submits an atomic command, so calling open() on all the child plans at the same time
* will mean that the datasource may not be in the state expected by a later command within the batch. So,
* for a batch of commands, we only open() a later plan when we are finished with the previous plan to
* guarantee that the commands in the previous plan are completed before the commands in any subsequent
* plans are executed.
*/
openPlan();
}
// Execute nextBatch() on each plan in sequence
TupleBatch nextBatch = null;
do {
nextBatch = updatePlans[planIndex].nextBatch(); // Can throw BlockedException
List<List<?>> currentBatch = nextBatch.getTuples();
for (int i = 0; i < currentBatch.size(); i++, commandIndex++) {
updateCounts[commandIndex] = currentBatch.get(i);
}
} while (!nextBatch.getTerminationFlag());
// since we are done with the plan explicitly close it.
updatePlans[planIndex++].close();
} catch (BlockedException e){
throw e;
} catch (TeiidComponentException | TeiidProcessingException e) {
if (singleResult) {
throw e;
}
Throwable cause = e;
if (e.getCause() instanceof TranslatorBatchException) {
TranslatorBatchException tbe = (TranslatorBatchException)e.getCause();
for (int i = 0; i < tbe.getUpdateCounts().length; i++) {
updateCounts[commandIndex++] = Arrays.asList(tbe.getUpdateCounts()[i]);
}
}
updateCounts = Arrays.copyOf(updateCounts, commandIndex);
getContext().setBatchUpdateException(cause);
}
}
if (singleResult) {
long result = 0;
for (int i = 0; i < updateCounts.length; i++) {
int value = (Integer)updateCounts[i].get(0);
if (value == Statement.EXECUTE_FAILED) {
//this should not happen, but is possible should a translator return
//the batch results rather than throwing an exception
throw new TeiidProcessingException(QueryPlugin.Event.TEIID31199, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID31198));
}
if (value > 0) {
result += value;
}
}
TupleBatch batch = new TupleBatch(1, new List<?>[] {Arrays.asList((int)Math.min(Integer.MAX_VALUE, result)) });
batch.setTerminationFlag(true);
return batch;
}
// Add tuples to current batch
TupleBatch batch = new TupleBatch(1, updateCounts);
batch.setTerminationFlag(true);
return batch;
}
private void openPlan() throws TeiidComponentException,
TeiidProcessingException {
//we don't worry about controlling transactions around each command
//it should be something that doesn't require a wrapping transaction
//reset prior to updating the context
updatePlans[planIndex].reset();
if (this.contexts != null && !this.contexts.isEmpty()) {
CommandContext context = updatePlans[planIndex].getContext();
VariableContext vc = context.getVariableContext();
//ensure that we're dealing with the global context
//this is just a safe guard against the plan not correctly resetting the context
while (vc.getParentContext() != null) {
vc = vc.getParentContext();
}
vc.clear();
VariableContext currentValues = this.contexts.get(planIndex);
vc.putAll(currentValues);
}
updatePlans[planIndex].open();
planOpened[planIndex] = true;
}
/**
* @see org.teiid.query.processor.ProcessorPlan#close()
* @since 4.2
*/
public void close() throws TeiidComponentException {
// if the plan opened but the atomic request got cancelled then close the last plan node.
if (planIndex < updatePlans.length && planOpened[planIndex]) {
updatePlans[planIndex].close();
}
}
/**
* @see org.teiid.query.processor.ProcessorPlan#reset()
* @since 4.2
*/
public void reset() {
super.reset();
for (int i = 0; i < updatePlans.length; i++) {
updatePlans[i].reset();
planOpened[i] = false;
}
for (int i = 0; i < updateCounts.length; i++) {
updateCounts[i] = null;
}
planIndex = 0;
commandIndex = 0;
}
public PlanNode getDescriptionProperties() {
PlanNode props = super.getDescriptionProperties();
for (int i = 0; i < getPlanCount(); i++) {
props.addProperty("Batch Plan " + i, updatePlans[i].getDescriptionProperties()); //$NON-NLS-1$
}
return props;
}
public String toString() {
StringBuffer val = new StringBuffer("BatchedUpdatePlan {\n"); //$NON-NLS-1$
for (int i = 0; i < getPlanCount(); i++) {
val.append(updatePlans[i])
.append("\n"); //$NON-NLS-1$
}
val.append("}\n"); //$NON-NLS-1$
return val.toString();
}
private int getPlanCount() {
return (contexts != null?1:updatePlans.length);
}
/**
* Returns the child plans for this batch. Used primarily for unit tests.
* @return
* @since 4.2
*/
public List getUpdatePlans() {
return Arrays.asList(updatePlans);
}
@Override
public Boolean requiresTransaction(boolean transactionalReads) {
if (!singleResult) {
//the transaction boundary should be around each command
return false;
}
boolean possible = false;
for (int i = 0; i < updatePlans.length; i++) {
Boolean requires = updatePlans[0].requiresTransaction(transactionalReads);
if (requires != null) {
if (requires) {
return true;
}
} else {
if (possible) {
return true;
}
possible = true;
}
}
if (possible) {
return null;
}
return false;
}
public void setSingleResult(boolean singleResult) {
this.singleResult = singleResult;
}
}