/*
* 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.tempdata;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.teiid.api.exception.query.ExpressionEvaluationException;
import org.teiid.common.buffer.*;
import org.teiid.common.buffer.BufferManager.BufferReserveMode;
import org.teiid.common.buffer.BufferManager.TupleSourceType;
import org.teiid.common.buffer.STree.InsertMode;
import org.teiid.common.buffer.TupleBuffer.TupleBufferTupleSource;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.TeiidRuntimeException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.query.QueryPlugin;
import org.teiid.query.eval.Evaluator;
import org.teiid.query.metadata.TempMetadataID;
import org.teiid.query.processor.CollectionTupleSource;
import org.teiid.query.processor.relational.RelationalNode;
import org.teiid.query.processor.relational.SortUtility;
import org.teiid.query.processor.relational.SortUtility.Mode;
import org.teiid.query.sql.lang.CacheHint;
import org.teiid.query.sql.lang.Criteria;
import org.teiid.query.sql.lang.OrderBy;
import org.teiid.query.sql.lang.SetClauseList;
import org.teiid.query.sql.symbol.AggregateSymbol;
import org.teiid.query.sql.symbol.Array;
import org.teiid.query.sql.symbol.ElementSymbol;
import org.teiid.query.sql.symbol.Expression;
import org.teiid.query.sql.symbol.ExpressionSymbol;
import org.teiid.query.util.CommandContext;
import org.teiid.query.util.GeneratedKeysImpl;
/**
* A Teiid Temp Table
* TODO: in this implementation blocked exceptions will not happen
* allowing for subquery evaluation though would cause pauses
*/
public class TempTable implements Cloneable, SearchableTable {
private final class InsertUpdateProcessor extends UpdateProcessor {
private boolean addRowId;
private int[] indexes;
private GeneratedKeysImpl keys;
private boolean upsert;
private TupleBuffer upsertUndoLog;
private InsertUpdateProcessor(TupleSource ts, boolean addRowId, int[] indexes, boolean canUndo, boolean upsert)
throws TeiidComponentException {
super(null, ts, canUndo);
if (upsert && addRowId) {
throw new AssertionError("invalid state"); //$NON-NLS-1$
}
this.addRowId = addRowId;
this.indexes = indexes;
this.upsert = upsert;
if (canUndo && upsert) {
this.upsertUndoLog = bm.createTupleBuffer(columns, sessionID, TupleSourceType.PROCESSOR);
}
}
@Override
long process() throws ExpressionEvaluationException,
TeiidComponentException, TeiidProcessingException {
tree.setBatchInsert(addRowId);
return super.process();
}
@Override
protected void afterCompletion(boolean success) throws TeiidComponentException {
if (!success && upsertUndoLog != null) {
upsertUndoLog.setFinal(true);
TupleBufferTupleSource undoTs = upsertUndoLog.createIndexedTupleSource();
undoTs.setReverse(true);
List<?> tuple = null;
try {
while ((tuple = undoTs.nextTuple()) != null) {
try {
updateTuple(tuple);
} catch (TeiidException e) {
LogManager.logError(LogConstants.CTX_DQP, e, e.getMessage());
}
}
} catch (TeiidProcessingException e) {
//shouldn't happen
throw new TeiidComponentException(e);
}
}
tree.setBatchInsert(false);
}
@Override
protected void tuplePassed(List tuple) throws BlockedException,
TeiidComponentException, TeiidProcessingException {
List<Object> generatedKey = null;
if (indexes != null) {
List<Object> newTuple = new ArrayList<Object>(columns.size());
if (keys != null) {
generatedKey = new ArrayList<Object>(keys.getColumnNames().length);
}
if (addRowId) {
newTuple.add(rowId.getAndIncrement());
}
for (int i = 0; i < indexes.length; i++) {
if (indexes[i] == -1) {
AtomicInteger sequence = sequences.get(i + (addRowId?1:0));
if (sequence != null) {
int val = sequence.getAndIncrement();
if (generatedKey != null && i < tree.getKeyLength()) {
generatedKey.add(val);
}
newTuple.add(val);
} else {
newTuple.add(null);
}
} else {
newTuple.add(tuple.get(indexes[i]));
}
}
tuple = newTuple;
} else if (addRowId) {
tuple = new ArrayList<Object>(tuple);
tuple.add(0, rowId.getAndIncrement());
}
currentTuple = tuple;
validateNotNull(tuple);
if (upsert) {
//TODO: we're potentially wasting a sequence value here
List<?> existing = tree.insert(tuple, indexes == null?InsertMode.UPDATE:InsertMode.NEW, -1);
if (existing != null && indexes != null) {
for (int i = 0; i < indexes.length; i++) {
if (indexes[i] == -1) {
AtomicInteger sequence = sequences.get(i + (addRowId?1:0));
if (sequence == null) {
tuple.set(i, existing.get(i));
}
}
}
tree.insert(tuple, InsertMode.UPDATE, -1);
}
upsertUndoLog.addTuple(tuple);
//don't add to main undo log
currentTuple = null;
return;
}
insertTuple(tuple, addRowId, true);
if (generatedKey != null) {
this.keys.addKey(generatedKey);
}
}
@Override
protected void undo(List<?> tuple) throws TeiidComponentException,
TeiidProcessingException {
deleteTuple(tuple);
}
public void setGeneratedKeys(GeneratedKeysImpl keys) {
this.keys = keys;
}
@Override
public void close() {
super.close();
if (this.upsertUndoLog != null) {
this.upsertUndoLog.remove();
}
}
}
private final class QueryTupleSource implements TupleSource {
private final Evaluator eval;
private final Criteria condition;
private final boolean project;
private final int[] indexes;
private int reserved;
private TupleBrowser browser;
private QueryTupleSource(TupleBrowser browser, Map map,
List<? extends Expression> projectedCols, Criteria condition) {
this.browser = browser;
this.indexes = RelationalNode.getProjectionIndexes(map, projectedCols);
this.eval = new Evaluator(map, null, null);
this.condition = condition;
this.project = shouldProject();
this.reserved = reserveBuffers();
if (updatable) {
lock.readLock().lock();
}
}
@Override
public List<?> nextTuple() throws TeiidComponentException,
TeiidProcessingException {
for (;;) {
List<?> next = browser.nextTuple();
if (next == null) {
bm.releaseBuffers(reserved);
reserved = 0;
return null;
}
if (condition != null && !eval.evaluate(condition, next)) {
continue;
}
if (project) {
next = RelationalNode.projectTuple(indexes, next);
}
return next;
}
}
@Override
public void closeSource() {
if (updatable) {
lock.readLock().unlock();
}
bm.releaseBuffers(reserved);
reserved = 0;
browser.closeSource();
}
private boolean shouldProject() {
if (indexes.length == getColumns().size()) {
for (int i = 0; i < indexes.length; i++) {
if (indexes[i] != i) {
return true;
}
}
return false;
}
return true;
}
}
private abstract class UpdateProcessor {
private TupleSource ts;
protected Evaluator eval;
private Criteria crit;
protected long updateCount = 0;
protected List currentTuple;
protected TupleBuffer undoLog;
UpdateProcessor(Criteria crit, TupleSource ts, boolean canUndo) throws TeiidComponentException {
this.ts = ts;
this.eval = new Evaluator(columnMap, null, null);
this.crit = crit;
if (canUndo) {
this.undoLog = bm.createTupleBuffer(columns, sessionID, TupleSourceType.PROCESSOR);
}
}
long process() throws ExpressionEvaluationException, TeiidComponentException, TeiidProcessingException {
int reserved = reserveBuffers();
lock.writeLock().lock();
boolean success = false;
try {
while (currentTuple != null || (currentTuple = ts.nextTuple()) != null) {
if (crit == null || eval.evaluate(crit, currentTuple)) {
tuplePassed(currentTuple);
updateCount++;
if (undoLog != null && currentTuple != null) {
undoLog.addTuple(currentTuple);
}
}
currentTuple = null;
}
bm.releaseBuffers(reserved);
reserved = 0;
success();
success = true;
} finally {
try {
afterCompletion(success);
if (!success && undoLog != null) {
undoLog.setFinal(true);
TupleBufferTupleSource undoTs = undoLog.createIndexedTupleSource();
undoTs.setReverse(true);
List<?> tuple = null;
while ((tuple = undoTs.nextTuple()) != null) {
try {
undo(tuple);
} catch (TeiidException e) {
LogManager.logError(LogConstants.CTX_DQP, e, e.getMessage());
}
}
}
} finally {
bm.releaseBuffers(reserved);
lock.writeLock().unlock();
close();
}
}
return updateCount;
}
/**
*
* @param success
* @throws TeiidComponentException
*/
protected void afterCompletion(boolean success) throws TeiidComponentException {
}
@SuppressWarnings("unused")
void success() throws TeiidComponentException, ExpressionEvaluationException, TeiidProcessingException {}
protected abstract void tuplePassed(List tuple) throws BlockedException, TeiidComponentException, TeiidProcessingException;
protected abstract void undo(List<?> tuple) throws TeiidComponentException, TeiidProcessingException;
public void close() {
ts.closeSource();
ts = null;
if (undoLog != null) {
undoLog.remove();
}
}
}
private static AtomicLong ID_GENERATOR = new AtomicLong();
private Long id = ID_GENERATOR.getAndIncrement();
private STree tree;
private AtomicLong rowId;
private List<ElementSymbol> columns;
private BufferManager bm;
private String sessionID;
private TempMetadataID tid;
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private boolean updatable = true;
private LinkedHashMap<List<ElementSymbol>, TempTable> indexTables;
private int keyBatchSize;
private int leafBatchSize;
private Map<Expression, Integer> columnMap;
private int[] notNull;
private Map<Integer, AtomicInteger> sequences;
private int uniqueColIndex;
private AtomicInteger activeReaders = new AtomicInteger();
TempTable(TempMetadataID tid, BufferManager bm, List<ElementSymbol> columns, int primaryKeyLength, String sessionID) {
this.tid = tid;
this.bm = bm;
int startIndex = 0;
if (primaryKeyLength == 0) {
startIndex = 1;
ElementSymbol rid = new ElementSymbol("rowId"); //$NON-NLS-1$
rid.setType(DataTypeManager.DefaultDataClasses.LONG);
columns.add(0, rid);
rowId = new AtomicLong();
tree = bm.createSTree(columns, sessionID, 1);
} else {
this.uniqueColIndex = primaryKeyLength;
tree = bm.createSTree(columns, sessionID, primaryKeyLength);
}
this.tree.setMinStorageSize(0);
this.columnMap = RelationalNode.createLookupMap(columns);
this.columns = columns;
IntBuffer notNullList = IntBuffer.allocate(columns.size());
if (!tid.getElements().isEmpty()) {
//not relevant for indexes
for (int i = startIndex; i < columns.size(); i++) {
TempMetadataID col = (TempMetadataID) columns.get(i).getMetadataID();
if (col == null) {
continue;
}
if (col.isAutoIncrement()) {
if (this.sequences == null) {
this.sequences = new HashMap<Integer, AtomicInteger>();
}
sequences.put(i, new AtomicInteger(1));
}
if (col.isNotNull()) {
notNullList.put(i);
}
}
}
this.notNull = Arrays.copyOf(notNullList.array(), notNullList.position());
if (this.sequences == null) {
this.sequences = Collections.emptyMap();
}
this.sessionID = sessionID;
this.keyBatchSize = bm.getSchemaSize(columns.subList(0, primaryKeyLength));
this.leafBatchSize = bm.getSchemaSize(columns);
tid.setCardinality(0);
}
private void validateNotNull(List tuple)
throws TeiidProcessingException {
for (int i = 0; i < notNull.length; i++) {
if (tuple.get(notNull[i]) == null) {
throw new TeiidProcessingException(QueryPlugin.Event.TEIID30236, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30236, columns.get(i)));
}
}
}
public TempTable clone() {
lock.readLock().lock();
try {
TempTable clone = (TempTable) super.clone();
clone.lock = new ReentrantReadWriteLock();
if (clone.indexTables != null) {
clone.indexTables = new LinkedHashMap<List<ElementSymbol>, TempTable>(clone.indexTables);
for (Map.Entry<List<ElementSymbol>, TempTable> entry : clone.indexTables.entrySet()) {
TempTable indexClone = entry.getValue().clone();
indexClone.lock = clone.lock;
entry.setValue(indexClone);
}
}
clone.tree = tree.clone();
clone.activeReaders = new AtomicInteger();
return clone;
} catch (CloneNotSupportedException e) {
throw new TeiidRuntimeException(e);
} finally {
lock.readLock().unlock();
}
}
public AtomicInteger getActive() {
return activeReaders;
}
void addIndex(List<ElementSymbol> indexColumns, boolean unique) throws TeiidComponentException, TeiidProcessingException {
List<ElementSymbol> keyColumns = columns.subList(0, tree.getKeyLength());
if (keyColumns.equals(indexColumns) || (indexTables != null && indexTables.containsKey(indexColumns))) {
return;
}
TempTable indexTable = createIndexTable(indexColumns, unique);
//TODO: ordered insert optimization
TupleSource ts = createTupleSource(indexTable.getColumns(), null, null);
indexTable.insert(ts, indexTable.getColumns(), false, false, null);
indexTable.getTree().compact();
}
private TempTable createIndexTable(List<ElementSymbol> indexColumns,
boolean unique) {
List<ElementSymbol> allColumns = new ArrayList<ElementSymbol>(indexColumns);
for (ElementSymbol elementSymbol : columns.subList(0, tree.getKeyLength())) {
if (allColumns.indexOf(elementSymbol) < 0) {
allColumns.add(elementSymbol);
}
}
TempTable indexTable = new TempTable(new TempMetadataID("idx", Collections.EMPTY_LIST), this.bm, allColumns, allColumns.size(), this.sessionID); //$NON-NLS-1$
indexTable.setPreferMemory(this.tree.isPreferMemory());
indexTable.lock = this.lock;
if (unique) {
indexTable.uniqueColIndex = indexColumns.size();
}
if (indexTables == null) {
indexTables = new LinkedHashMap<List<ElementSymbol>, TempTable>();
indexTables.put(indexColumns, indexTable);
}
indexTable.setUpdatable(this.updatable);
return indexTable;
}
private int reserveBuffers() {
return bm.reserveBuffers(leafBatchSize + (tree.getHeight() - 1)*keyBatchSize, BufferReserveMode.FORCE);
}
public TupleSource createTupleSource(final List<? extends Expression> projectedCols, final Criteria condition, OrderBy orderBy) throws TeiidComponentException, TeiidProcessingException {
//special handling for count(*)
boolean agg = false;
for (Expression singleElementSymbol : projectedCols) {
if (singleElementSymbol instanceof ExpressionSymbol && ((ExpressionSymbol)singleElementSymbol).getExpression() instanceof AggregateSymbol) {
agg = true;
break;
}
}
if (agg) {
if (condition == null) {
long count = this.getRowCount();
return new CollectionTupleSource(Arrays.asList(Collections.nCopies(projectedCols.size(), (int)Math.min(Integer.MAX_VALUE, count))).iterator());
}
orderBy = null;
}
IndexInfo primary = new IndexInfo(this, projectedCols, condition, orderBy, true);
IndexInfo ii = primary;
if (indexTables != null && (condition != null || orderBy != null) && ii.valueSet.size() != 1) {
LogManager.logDetail(LogConstants.CTX_DQP, "Considering indexes on table", this, "for query", projectedCols, condition, orderBy); //$NON-NLS-1$ //$NON-NLS-2$
long rowCost = this.tree.getRowCount();
long bestCost = estimateCost(orderBy, ii, rowCost);
for (TempTable table : this.indexTables.values()) {
IndexInfo secondary = new IndexInfo(table, projectedCols, condition, orderBy, false);
long cost = estimateCost(orderBy, secondary, rowCost);
if (cost < bestCost) {
ii = secondary;
bestCost = cost;
}
}
LogManager.logDetail(LogConstants.CTX_DQP, "Choose index", ii.table, "covering:", ii.coveredCriteria,"ordering:", ii.ordering); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (ii.covering) {
return ii.table.createTupleSource(projectedCols, condition, orderBy, ii, agg);
}
List<ElementSymbol> pkColumns = this.columns.subList(0, this.tree.getKeyLength());
if (ii.ordering != null) {
//use order and join
primary.valueTs = ii.table.createTupleSource(pkColumns,
ii.coveredCriteria, orderBy, ii, agg);
primary.ordering = null;
return createTupleSource(projectedCols, ii.nonCoveredCriteria, null, primary, agg);
}
//order by pk to localize lookup costs, then join
OrderBy pkOrderBy = new OrderBy();
for (ElementSymbol elementSymbol : pkColumns) {
pkOrderBy.addVariable(elementSymbol);
}
primary.valueTs = ii.table.createTupleSource(pkColumns,
ii.coveredCriteria, pkOrderBy, ii, agg);
return createTupleSource(projectedCols, ii.nonCoveredCriteria, orderBy, primary, agg);
}
return createTupleSource(projectedCols, condition, orderBy, ii, agg);
}
private TupleSource createTupleSource(
final List<? extends Expression> projectedCols,
final Criteria condition, OrderBy orderBy, IndexInfo ii, boolean agg)
throws TeiidComponentException, TeiidProcessingException {
TupleBrowser browser = ii.createTupleBrowser(bm.getOptions().getDefaultNullOrder(), true);
TupleSource ts = new QueryTupleSource(browser, columnMap, agg?getColumns():projectedCols, condition);
boolean usingQueryTupleSource = false;
boolean success = false;
TupleBuffer tb = null;
try {
if (ii.ordering == null && orderBy != null) {
SortUtility sort = new SortUtility(ts, orderBy.getOrderByItems(), Mode.SORT, bm, sessionID, projectedCols);
sort.setNonBlocking(true);
tb = sort.sort();
} else if (agg) {
int count = 0;
while (ts.nextTuple() != null) {
count++;
}
success = true;
return new CollectionTupleSource(Arrays.asList(Collections.nCopies(projectedCols.size(), count)).iterator());
} else if (updatable) {
tb = bm.createTupleBuffer(projectedCols, sessionID, TupleSourceType.PROCESSOR);
List<?> next = null;
while ((next = ts.nextTuple()) != null) {
tb.addTuple(next);
}
} else {
usingQueryTupleSource = true;
success = true;
return ts;
}
tb.close();
success = true;
return tb.createIndexedTupleSource(true);
} finally {
if (!success && tb != null) {
tb.remove();
}
if (!usingQueryTupleSource) {
//ensure the buffers get released
ts.closeSource();
}
}
}
/**
* TODO: this could easily use statistics - the tree level 1 would be an ideal place
* to compute them, since it minimizes page loads, and is a random sample.
* TODO: this should also factor in the block size
*/
private long estimateCost(OrderBy orderBy, IndexInfo ii, long rowCost) {
long initialCost = rowCost;
long additionalCost = 0;
if (ii.valueSet.size() != 0) {
int length = ii.valueSet.get(0).size();
rowCost = ii.valueSet.size();
additionalCost = rowCost * (64 - Long.numberOfLeadingZeros(initialCost - 1));
if (ii.table.uniqueColIndex != length) {
rowCost *= 3*(ii.table.uniqueColIndex - length);
}
if (rowCost > initialCost) {
additionalCost = rowCost - initialCost;
rowCost = initialCost;
}
} else if (ii.upper != null) {
additionalCost = (64 - Long.numberOfLeadingZeros(initialCost - 1));
rowCost /= 3;
} else if (ii.lower != null) {
additionalCost = (64 - Long.numberOfLeadingZeros(initialCost - 1));
rowCost /= 3;
}
if (rowCost > 1 && !ii.covering) {
//primary lookup
additionalCost += rowCost * (64 - Long.numberOfLeadingZeros(rowCost - 1));
}
if (rowCost > 1 && orderBy != null && ii.ordering != null) {
//pk order or non-covered ordering
//TODO: this should be based upon the filtered rowCost, but instead it is
//written as a bonus
additionalCost -= Math.min(additionalCost, rowCost * (64 - Long.numberOfLeadingZeros(rowCost - 1)));
}
return rowCost + additionalCost;
}
private TupleBrowser createTupleBrower(Criteria condition, boolean direction) throws TeiidComponentException {
IndexInfo ii = new IndexInfo(this, null, condition, null, true);
ii.ordering = direction;
return ii.createTupleBrowser(bm.getOptions().getDefaultNullOrder(), false);
}
public long getRowCount() {
return tree.getRowCount();
}
public long truncate(boolean force) {
this.tid.getTableData().dataModified(tree.getRowCount());
return tree.truncate(force);
}
public void remove() {
lock.writeLock().lock();
try {
tid.getTableData().removed();
tree.remove();
if (this.indexTables != null) {
for (TempTable indexTable : this.indexTables.values()) {
indexTable.remove();
}
}
} finally {
lock.writeLock().unlock();
}
}
@Override
public Object matchesPkColumn(int pkIndex, Expression ex) {
if (rowId != null) {
return false;
}
if (ex instanceof Array) {
Array array = (Array)ex;
List<Expression> exprs = array.getExpressions();
int toIndex = Math.min(this.getPkLength(), exprs.size());
int[] indexes = new int[toIndex];
for (int i = pkIndex; i < toIndex; i++) {
int index = exprs.indexOf(this.columns.get(i));
indexes[i] = index;
if (index == -1) {
if (i == pkIndex) {
return false;
}
break;
}
}
return indexes;
}
return columns.get(pkIndex).equals(ex);
}
@Override
public boolean supportsOrdering(int pkIndex, Expression ex) {
//all indexes are currently ordered
return true;
}
public List<ElementSymbol> getColumns() {
if (rowId != null) {
return columns.subList(1, columns.size());
}
return columns;
}
public TupleSource insert(TupleSource tuples, final List<ElementSymbol> variables, boolean canUndo, boolean upsert, CommandContext context) throws TeiidComponentException, ExpressionEvaluationException, TeiidProcessingException {
List<ElementSymbol> cols = getColumns();
final int[] indexes = new int[cols.size()];
boolean shouldProject = false;
for (int i = 0; i < cols.size(); i++) {
indexes[i] = variables.indexOf(cols.get(i));
shouldProject |= (indexes[i] != i);
}
InsertUpdateProcessor up = new InsertUpdateProcessor(tuples, rowId != null, shouldProject?indexes:null, canUndo, upsert);
if (context != null && context.isReturnAutoGeneratedKeys() && rowId == null) {
List<String> colNames = null;
List<Class<?>> colTypes = null;
for (int i = 0; i < tree.getKeyLength(); i++) {
TempMetadataID col = tid.getElements().get(i);
if (col.isAutoIncrement() && indexes[i] == -1) {
if (colNames == null) {
colNames = new ArrayList<String>();
colTypes = new ArrayList<Class<?>>();
}
colNames.add(col.getName());
colTypes.add(col.getType());
break;
}
}
if (colNames != null) {
GeneratedKeysImpl keys = context.returnGeneratedKeys(colNames.toArray(new String[colNames.size()]), colTypes.toArray(new Class<?>[colTypes.size()]));
up.setGeneratedKeys(keys);
}
}
long updateCount = up.process();
tid.setCardinality(tree.getRowCount());
tid.getTableData().dataModified(updateCount);
return CollectionTupleSource.createUpdateCountArrayTupleSource(updateCount);
}
public TupleSource update(Criteria crit, final SetClauseList update) throws TeiidComponentException, ExpressionEvaluationException, TeiidProcessingException {
final boolean primaryKeyChangePossible = canChangePrimaryKey(update);
final TupleBrowser browser = createTupleBrower(crit, OrderBy.ASC);
UpdateProcessor up = new UpdateProcessor(crit, browser, true) {
protected TupleBuffer changeSet;
protected UpdateProcessor changeSetProcessor;
@Override
protected void tuplePassed(List tuple)
throws BlockedException, TeiidComponentException, TeiidProcessingException {
List<Object> newTuple = new ArrayList<Object>(tuple);
for (Map.Entry<ElementSymbol, Expression> entry : update.getClauseMap().entrySet()) {
newTuple.set(columnMap.get(entry.getKey()), eval.evaluate(entry.getValue(), tuple));
}
validateNotNull(newTuple);
if (primaryKeyChangePossible) {
browser.removed();
deleteTuple(tuple);
if (changeSet == null) {
changeSet = bm.createTupleBuffer(columns, sessionID, TupleSourceType.PROCESSOR);
}
changeSet.addTuple(newTuple);
} else {
browser.update(newTuple);
}
}
@Override
protected void undo(List<?> tuple) throws TeiidComponentException, TeiidProcessingException {
if (primaryKeyChangePossible) {
insertTuple(tuple, false, true);
} else {
updateTuple(tuple);
}
}
@Override
void success() throws TeiidComponentException, ExpressionEvaluationException, TeiidProcessingException {
//existing tuples have been removed
//changeSet contains possible updates
if (primaryKeyChangePossible) {
if (changeSetProcessor == null) {
changeSetProcessor = new InsertUpdateProcessor(changeSet.createIndexedTupleSource(true), false, null, true, false);
}
changeSetProcessor.process(); //when this returns, we're up to date
}
}
@Override
public void close() {
super.close();
changeSetProcessor = null;
if (changeSet != null) {
changeSet.remove();
changeSet = null;
}
}
};
long updateCount = up.process();
tid.getTableData().dataModified(updateCount);
return CollectionTupleSource.createUpdateCountTupleSource((int)Math.min(Integer.MAX_VALUE, updateCount));
}
private boolean canChangePrimaryKey(final SetClauseList update) {
if (rowId == null) {
Set<ElementSymbol> affectedColumns = new HashSet<ElementSymbol>(update.getClauseMap().keySet());
affectedColumns.retainAll(columns.subList(0, tree.getKeyLength()));
if (!affectedColumns.isEmpty()) {
return true;
}
}
return false;
}
public TupleSource delete(Criteria crit) throws TeiidComponentException, ExpressionEvaluationException, TeiidProcessingException {
final TupleBrowser browser = createTupleBrower(crit, OrderBy.ASC);
UpdateProcessor up = new UpdateProcessor(crit, browser, true) {
@Override
protected void tuplePassed(List tuple)
throws ExpressionEvaluationException,
BlockedException, TeiidComponentException {
browser.removed();
deleteTuple(tuple);
}
@Override
protected void undo(List<?> tuple) throws TeiidComponentException, TeiidProcessingException {
insertTuple(tuple, false, true);
}
};
long updateCount = up.process();
tid.setCardinality(tree.getRowCount());
tid.getTableData().dataModified(updateCount);
return CollectionTupleSource.createUpdateCountTupleSource((int)Math.min(Integer.MAX_VALUE, updateCount));
}
boolean insertTuple(List<?> list, boolean ordered, boolean checkDuplidate) throws TeiidComponentException, TeiidProcessingException {
if (tree.insert(list, ordered?InsertMode.ORDERED:InsertMode.NEW, -1) != null) {
if (!checkDuplidate) {
return false;
}
throw new TeiidProcessingException(QueryPlugin.Event.TEIID30238, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30238, this.tid.getID()));
}
return true;
}
private void deleteTuple(List<?> tuple) throws TeiidComponentException {
if (tree.remove(tuple) == null) {
throw new AssertionError("Delete failed"); //$NON-NLS-1$
}
}
void writeTo(ObjectOutputStream oos) throws TeiidComponentException, IOException {
this.lock.readLock().lock();
try {
this.tree.writeValuesTo(oos);
if (this.indexTables == null) {
oos.writeInt(0);
} else {
oos.writeInt(this.indexTables.size());
for (Map.Entry<List<ElementSymbol>, TempTable> entry : this.indexTables.entrySet()) {
oos.writeBoolean(entry.getValue().uniqueColIndex > 0);
oos.writeInt(entry.getKey().size());
for (ElementSymbol es : entry.getKey()) {
oos.writeInt(this.columnMap.get(es));
}
entry.getValue().writeTo(oos);
}
}
} finally {
this.lock.readLock().unlock();
}
}
void readFrom(ObjectInputStream ois) throws TeiidComponentException, IOException, ClassNotFoundException {
this.tree.readValuesFrom(ois);
int numIdx = ois.readInt();
for (int i = 0; i < numIdx; i++) {
boolean unique = ois.readBoolean();
int numCols = ois.readInt();
ArrayList<ElementSymbol> indexColumns = new ArrayList<ElementSymbol>(numCols);
for (int j = 0; j < numCols; j++) {
int colIndex = ois.readInt();
indexColumns.add(this.columns.get(colIndex));
}
TempTable tt = this.createIndexTable(indexColumns, unique);
tt.readFrom(ois);
}
}
List<?> updateTuple(List<?> tuple, boolean remove) throws TeiidComponentException {
try {
lock.writeLock().lock();
if (remove) {
List<?> result = tree.remove(tuple);
if (result == null) {
return null;
}
if (indexTables != null) {
for (TempTable index : this.indexTables.values()) {
tuple = RelationalNode.projectTuple(RelationalNode.getProjectionIndexes(index.getColumnMap(), index.columns), result);
index.tree.remove(tuple);
}
}
tid.getTableData().dataModified(1);
return result;
}
List<?> result = tree.insert(tuple, InsertMode.UPDATE, -1);
if (indexTables != null) {
for (TempTable index : this.indexTables.values()) {
tuple = RelationalNode.projectTuple(RelationalNode.getProjectionIndexes(index.getColumnMap(), index.columns), tuple);
index.tree.insert(tuple, InsertMode.UPDATE, -1);
}
}
tid.getTableData().dataModified(1);
return result;
} finally {
lock.writeLock().unlock();
}
}
private void updateTuple(List<?> tuple) throws TeiidComponentException {
if (tree.insert(tuple, InsertMode.UPDATE, -1) == null) {
throw new AssertionError("Update failed"); //$NON-NLS-1$
}
}
void setPreferMemory(boolean preferMemory) {
this.tree.setPreferMemory(preferMemory);
}
void setUpdatable(boolean updatable) {
this.updatable = updatable;
if (this.indexTables != null) {
for (TempTable index : this.indexTables.values()) {
index.setUpdatable(updatable);
}
}
}
CacheHint getCacheHint() {
return this.tid.getCacheHint();
}
public int getPkLength() {
if (rowId != null) {
return 0;
}
return this.tree.getKeyLength();
}
public boolean isUpdatable() {
return updatable;
}
@Override
public String toString() {
return tid.getID() + " (" + columns + ")\n"; //$NON-NLS-1$ //$NON-NLS-2$
}
public Map<Expression, Integer> getColumnMap() {
return this.columnMap;
}
STree getTree() {
return tree;
}
public TempMetadataID getMetadataId() {
return tid;
}
public Long getId() {
return id;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof TempTable)) {
return false;
}
TempTable other = (TempTable)obj;
return id.equals(other.id);
}
}