/*
* 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.common.buffer;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import org.teiid.client.ResizingArrayList;
import org.teiid.common.buffer.LobManager.ReferenceMode;
import org.teiid.core.TeiidComponentException;
import org.teiid.core.TeiidProcessingException;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.Streamable;
import org.teiid.core.util.Assertion;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.query.QueryPlugin;
import org.teiid.query.sql.symbol.Expression;
public class TupleBuffer {
public class TupleBufferTupleSource extends
AbstractTupleSource {
private final boolean singleUse;
private boolean noBlocking;
private boolean reverse;
private TupleBufferTupleSource(boolean singleUse) {
this.singleUse = singleUse;
}
@Override
protected List<?> finalRow() throws TeiidComponentException, TeiidProcessingException {
if(isFinal || noBlocking || reverse) {
return null;
}
throw BlockedException.blockWithTrace("Blocking on non-final TupleBuffer", tupleSourceID, "size", getRowCount()); //$NON-NLS-1$ //$NON-NLS-2$
}
@Override
protected long available() {
if (!reverse) {
return rowCount - getCurrentIndex() + 1;
}
return getCurrentIndex();
}
@Override
protected TupleBatch getBatch(long row) throws TeiidComponentException {
return TupleBuffer.this.getBatch(row);
}
@Override
public void closeSource() {
super.closeSource();
if (singleUse) {
remove();
}
}
public void setNoBlocking(boolean noBlocking) {
this.noBlocking = noBlocking;
}
public void setReverse(boolean reverse) {
this.reverse = reverse;
}
@Override
public long getCurrentIndex() {
if (!reverse) {
return super.getCurrentIndex();
}
return getRowCount() - super.getCurrentIndex() + 1;
}
}
/**
* Gets the data type names for each of the input expressions, in order.
* @param expressions List of Expressions
* @return
* @since 4.2
*/
public static String[] getTypeNames(List<? extends Expression> expressions) {
if (expressions == null) {
return null;
}
String[] types = new String[expressions.size()];
for (ListIterator<? extends Expression> i = expressions.listIterator(); i.hasNext();) {
Expression expr = i.next();
types[i.previousIndex()] = DataTypeManager.getDataTypeName(expr.getType());
}
return types;
}
//construction state
private BatchManager manager;
private String tupleSourceID;
private List<? extends Expression> schema;
private int batchSize;
private long rowCount;
private boolean isFinal;
private TreeMap<Long, Long> batches = new TreeMap<Long, Long>();
private List<List<?>> batchBuffer;
private boolean removed;
private boolean forwardOnly;
private LobManager lobManager;
private String uuid;
public TupleBuffer(BatchManager manager, String id, List<? extends Expression> schema, LobManager lobManager, int batchSize) {
this.manager = manager;
this.tupleSourceID = id;
this.schema = schema;
this.lobManager = lobManager;
this.batchSize = batchSize;
}
public void setInlineLobs(boolean inline) {
if (this.lobManager != null) {
this.lobManager.setInlineLobs(inline);
}
}
public void removeLobTracking() {
if (this.lobManager != null) {
this.lobManager.remove();
this.lobManager = null;
}
}
public String getId() {
if (this.uuid == null) {
this.uuid = java.util.UUID.randomUUID().toString();
}
return this.uuid;
}
public void setId(String uuid) {
this.uuid = uuid;
}
public boolean isLobs() {
return lobManager != null;
}
public void addTuple(List<?> tuple) throws TeiidComponentException {
if (isLobs()) {
lobManager.updateReferences(tuple, ReferenceMode.CREATE);
}
this.rowCount++;
if (batchBuffer == null) {
batchBuffer = new ResizingArrayList<List<?>>(batchSize/4);
}
batchBuffer.add(tuple);
if (batchBuffer.size() == batchSize) {
saveBatch(false);
}
}
/**
* Adds the given batch preserving row offsets.
* @param batch
* @throws TeiidComponentException
*/
public void addTupleBatch(TupleBatch batch, boolean save) throws TeiidComponentException {
setRowCount(batch.getBeginRow() - 1);
List<List<?>> tuples = batch.getTuples();
if (save) {
for (int i = 0; i < batch.getRowCount(); i++) {
addTuple(tuples.get(i));
}
} else {
//add the lob references only, since they may still be referenced later
if (isLobs()) {
for (int i = 0; i < batch.getRowCount(); i++) {
lobManager.updateReferences(tuples.get(i), ReferenceMode.CREATE);
}
}
}
}
public void setRowCount(long rowCount)
throws TeiidComponentException {
assert this.rowCount <= rowCount;
if (this.rowCount != rowCount) {
saveBatch(true);
this.rowCount = rowCount;
}
}
public void purge() {
if (this.batchBuffer != null) {
this.batchBuffer.clear();
}
for (Long batch : this.batches.values()) {
this.manager.remove(batch);
}
if (this.lobManager != null) {
this.lobManager.remove();
}
this.batches.clear();
}
public void persistLobs() throws TeiidComponentException {
if (this.lobManager != null) {
this.lobManager.persist();
}
}
/**
* Force the persistence of any rows held in memory.
* @throws TeiidComponentException
*/
public void saveBatch() throws TeiidComponentException {
this.saveBatch(false);
}
void saveBatch(boolean force) throws TeiidComponentException {
Assertion.assertTrue(!this.isRemoved());
if (batchBuffer == null || batchBuffer.isEmpty() || (!force && batchBuffer.size() < Math.max(1, batchSize / 32))) {
return;
}
Long mbatch = manager.createManagedBatch(batchBuffer, null, false);
this.batches.put(rowCount - batchBuffer.size() + 1, mbatch);
batchBuffer = null;
}
public void close() throws TeiidComponentException {
saveBatch(false);
this.isFinal = true;
}
/**
* Get the batch containing the given row.
* NOTE: the returned batch may be empty or may begin with a row other
* than the one specified.
* @param row
* @return
* @throws TeiidComponentException
*
* TODO: a method to get the raw batch
*/
public TupleBatch getBatch(long row) throws TeiidComponentException {
assert !removed;
TupleBatch result = null;
if (row > rowCount) {
result = new TupleBatch(rowCount + 1, new List[] {});
} else if (this.batchBuffer != null && row > rowCount - this.batchBuffer.size()) {
result = new TupleBatch(rowCount - this.batchBuffer.size() + 1, batchBuffer);
if (forwardOnly) {
this.batchBuffer = null;
}
} else {
if (this.batchBuffer != null && !this.batchBuffer.isEmpty()) {
//this is just a sanity check to ensure we're not holding too many
//hard references to batches.
saveBatch(false);
}
Map.Entry<Long, Long> entry = batches.floorEntry(row);
Assertion.isNotNull(entry);
Long batch = entry.getValue();
List<List<?>> rows = manager.getBatch(batch, !forwardOnly);
result = new TupleBatch(entry.getKey(), rows);
if (isFinal && result.getEndRow() == rowCount) {
result.setTerminationFlag(true);
}
if (forwardOnly) {
batches.remove(entry.getKey());
}
}
if (isFinal && result.getEndRow() == rowCount) {
result.setTerminationFlag(true);
}
return result;
}
public void remove() {
if (!removed) {
if (LogManager.isMessageToBeRecorded(LogConstants.CTX_BUFFER_MGR, MessageLevel.DETAIL)) {
LogManager.logDetail(LogConstants.CTX_BUFFER_MGR, "Removing TupleBuffer:", this.tupleSourceID); //$NON-NLS-1$
}
this.batchBuffer = null;
purge();
this.manager.remove();
removed = true;
}
}
/**
* Returns the total number of rows contained in managed batches
* @return
*/
public long getManagedRowCount() {
if (!this.batches.isEmpty()) {
long start = this.batches.firstKey();
return rowCount - start + 1;
} else if (this.batchBuffer != null) {
return this.batchBuffer.size();
}
return 0;
}
/**
* Returns the last row number
* @return
*/
public long getRowCount() {
return rowCount;
}
public boolean isFinal() {
return isFinal;
}
public void setFinal(boolean isFinal) {
this.isFinal = isFinal;
}
public List<? extends Expression> getSchema() {
return schema;
}
public int getBatchSize() {
return batchSize;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
public Streamable<?> getLobReference(String id) throws TeiidComponentException {
if (lobManager == null) {
throw new TeiidComponentException(QueryPlugin.Event.TEIID30032, QueryPlugin.Util.gs(QueryPlugin.Event.TEIID30032));
}
return lobManager.getLobReference(id);
}
public void setForwardOnly(boolean forwardOnly) {
this.forwardOnly = forwardOnly;
}
public TupleBufferTupleSource createIndexedTupleSource() {
return createIndexedTupleSource(false);
}
/**
* Create a new iterator for this buffer
* @return
*/
public TupleBufferTupleSource createIndexedTupleSource(final boolean singleUse) {
if (singleUse) {
setForwardOnly(true);
}
return new TupleBufferTupleSource(singleUse);
}
@Override
public String toString() {
return this.tupleSourceID;
}
public boolean isRemoved() {
return removed;
}
public boolean isForwardOnly() {
return forwardOnly;
}
public void setPrefersMemory(boolean prefersMemory) {
this.manager.setPrefersMemory(prefersMemory);
}
public String[] getTypes() {
return manager.getTypes();
}
public int getLobCount() {
if (this.lobManager == null) {
return 0;
}
return this.lobManager.getLobCount();
}
public void truncateTo(int rowLimit) throws TeiidComponentException {
if (rowCount <= rowLimit) {
return;
}
//TODO this could be more efficient with handling the last batch
if (this.batchBuffer != null) {
for (int i = batchBuffer.size() - 1; i >= 0; i--) {
if (this.rowCount == rowLimit) {
break;
}
this.rowCount--;
List<?> tuple = this.batchBuffer.remove(i);
if (this.lobManager != null) {
this.lobManager.updateReferences(tuple, ReferenceMode.REMOVE);
}
}
}
TupleBatch last = null;
while (rowCount > rowLimit) {
last = this.getBatch(rowCount);
Long id = this.batches.remove(last.getBeginRow());
if (id != null) {
this.manager.remove(id);
}
if (this.lobManager != null) {
for (List<?> tuple : last.getTuples()) {
this.lobManager.updateReferences(tuple, ReferenceMode.REMOVE);
}
}
rowCount = last.getBeginRow() - 1;
}
if (rowCount < rowLimit) {
List<List<?>> tuples = last.getTuples();
int i = 0;
while (rowCount < rowLimit) {
addTuple(tuples.get(i++));
}
}
saveBatch(false);
}
/**
* Return a more accurate batch estimate or 0 if a new estimate is not available
*/
public int getRowSizeEstimate() {
return this.manager.getRowSizeEstimate();
}
}