package edu.washington.escience.myria.column.builder; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.sql.ResultSet; import java.sql.SQLException; import java.util.List; import java.util.Objects; import com.almworks.sqlite4java.SQLiteException; import com.almworks.sqlite4java.SQLiteStatement; import com.google.common.base.Preconditions; import edu.washington.escience.myria.Type; import edu.washington.escience.myria.column.BlobColumn; import edu.washington.escience.myria.column.mutable.BlobMutableColumn; import edu.washington.escience.myria.proto.DataProto.BlobColumnMessage; import edu.washington.escience.myria.proto.DataProto.ColumnMessage; import edu.washington.escience.myria.storage.TupleUtils; import edu.washington.escience.myria.util.MyriaUtils; /** * A column of Blob values. */ public final class BlobColumnBuilder extends ColumnBuilder<ByteBuffer> { /** * The internal representation of the data. */ private final ByteBuffer[] data; /** Number of elements in this column. */ private int numBB; /** * If the builder has built the column. */ private boolean built = false; /** Constructs an empty column that can hold up to TupleBatch.BATCH_SIZE elements. */ public BlobColumnBuilder() { numBB = 0; data = new ByteBuffer[TupleUtils.getBatchSize(Type.BLOB_TYPE)]; } /** * copy. * * @param numDates the actual num strings in the data * @param data the underlying data */ private BlobColumnBuilder(final ByteBuffer[] data, final int numBB) { this.numBB = numBB; this.data = data; } /* Constructs a BlobColumn by deserializing the given ColumnMessage. * @param message a ColumnMessage containing the contents of this column. * @param numTuples num tuples in the column message * @return the built column */ public static BlobColumn buildFromProtobuf(final ColumnMessage message, final int numTuples) { Preconditions.checkArgument( message.getType().ordinal() == ColumnMessage.Type.BLOB_VALUE, "Trying to construct BlobColumn from non-bytes ColumnMessage %s", message.getType()); Preconditions.checkArgument(message.hasBlobColumn(), "ColumnMessage is missing BlobColumn"); final BlobColumnMessage BlobColumn = message.getBlobColumn(); List<Integer> startIndices = BlobColumn.getStartIndicesList(); List<Integer> endIndices = BlobColumn.getEndIndicesList(); ByteBuffer[] newData = new ByteBuffer[numTuples]; ByteBuffer data = BlobColumn.getData().asReadOnlyByteBuffer(); for (int i = 0; i < numTuples; i++) { int length = endIndices.get(i) - startIndices.get(i); newData[i] = ByteBuffer.allocate(length); data.get(newData[i].array(), 0, length); } return new BlobColumnBuilder(newData, numTuples).build(); } @Override public BlobColumnBuilder appendBlob(ByteBuffer value) throws BufferOverflowException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); if (value == null) { value = ByteBuffer.allocate(0); } Objects.requireNonNull(value, "value"); if (numBB >= TupleUtils.getBatchSize(Type.BLOB_TYPE)) { throw new BufferOverflowException(); } data[numBB++] = value; return this; } @Override public Type getType() { return Type.BLOB_TYPE; } @Override public BlobColumnBuilder appendFromJdbc(final ResultSet resultSet, final int jdbcIndex) throws SQLException, BufferOverflowException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); return appendBlob(ByteBuffer.wrap(resultSet.getBytes(jdbcIndex))); } @Override public BlobColumnBuilder appendFromSQLite(final SQLiteStatement statement, final int index) throws SQLiteException, BufferOverflowException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); return appendBlob(ByteBuffer.wrap(statement.columnBlob(index))); } @Override public int size() { return numBB; } @Override public BlobColumn build() { built = true; return new BlobColumn(data, numBB); } @Override public BlobMutableColumn buildMutable() { built = true; return new BlobMutableColumn(data, numBB); } @Override public void replaceBlob(final ByteBuffer value, final int row) throws IndexOutOfBoundsException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); Preconditions.checkElementIndex(row, numBB); Preconditions.checkNotNull(value); data[row] = value; } @Override public BlobColumnBuilder expand(final int size) throws BufferOverflowException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); Preconditions.checkArgument(size >= 0); if (numBB + size > data.length) { throw new BufferOverflowException(); } numBB += size; return this; } @Override public BlobColumnBuilder expandAll() { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); numBB = data.length; return this; } @Override public ByteBuffer getBlob(final int row) { Preconditions.checkElementIndex(row, numBB); return data[row]; } @Override public ByteBuffer getObject(final int row) { return getBlob(row); } @Deprecated @Override public ColumnBuilder<ByteBuffer> appendObject(final Object value) throws BufferOverflowException { Preconditions.checkState( !built, "No further changes are allowed after the builder has built the column."); return appendBlob((ByteBuffer) MyriaUtils.ensureObjectIsValidType(value)); } @Override public BlobColumnBuilder forkNewBuilder() { ByteBuffer[] newData = new ByteBuffer[data.length]; System.arraycopy(data, 0, newData, 0, numBB); return new BlobColumnBuilder(newData, numBB); } }