blob: 0272107dd5f81edd6ec3ecfb8b3d5ab57e8cd7de [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.arrow.vector;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.arrow.memory.BaseAllocator;
import org.apache.arrow.memory.BufferAllocator;
import org.apache.arrow.memory.OutOfMemoryException;
import org.apache.arrow.memory.util.ArrowBufPointer;
import org.apache.arrow.memory.util.ByteFunctionHelpers;
import org.apache.arrow.util.Preconditions;
import org.apache.arrow.vector.compare.VectorVisitor;
import org.apache.arrow.vector.ipc.message.ArrowFieldNode;
import org.apache.arrow.vector.types.pojo.Field;
import org.apache.arrow.vector.util.CallBack;
import org.apache.arrow.vector.util.OversizedAllocationException;
import org.apache.arrow.vector.util.TransferPair;
import io.netty.buffer.ArrowBuf;
/**
* BaseVariableWidthVector is a base class providing functionality for variable width
* types (e.g. Lists, Strings, etc.)
*/
public abstract class BaseVariableWidthVector extends BaseValueVector
implements VariableWidthVector, FieldVector, VectorDefinitionSetter {
private static final int DEFAULT_RECORD_BYTE_COUNT = 8;
private static final int INITIAL_BYTE_COUNT = INITIAL_VALUE_ALLOCATION * DEFAULT_RECORD_BYTE_COUNT;
private int lastValueCapacity;
private int lastValueAllocationSizeInBytes;
/* protected members */
public static final int OFFSET_WIDTH = 4; /* 4 byte unsigned int to track offsets */
protected static final byte[] emptyByteArray = new byte[]{};
protected ArrowBuf validityBuffer;
protected ArrowBuf valueBuffer;
protected ArrowBuf offsetBuffer;
protected int valueCount;
protected int lastSet;
protected final Field field;
/**
* Constructs a new instance.
*
* @param field The field materialized by this vector.
* @param allocator The allocator to use for creating/resizing buffers
*/
public BaseVariableWidthVector(Field field, final BufferAllocator allocator) {
super(allocator);
this.field = field;
lastValueAllocationSizeInBytes = INITIAL_BYTE_COUNT;
// -1 because we require one extra slot for the offset array.
lastValueCapacity = INITIAL_VALUE_ALLOCATION - 1;
valueCount = 0;
lastSet = -1;
offsetBuffer = allocator.getEmpty();
validityBuffer = allocator.getEmpty();
valueBuffer = allocator.getEmpty();
}
@Override
public String getName() {
return field.getName();
}
/* TODO:
* see if getNullCount() can be made faster -- O(1)
*/
/* TODO:
* Once the entire hierarchy has been refactored, move common functions
* like getNullCount(), splitAndTransferValidityBuffer to top level
* base class BaseValueVector.
*
* Along with this, some class members (validityBuffer) can also be
* abstracted out to top level base class.
*
* Right now BaseValueVector is the top level base class for other
* vector types in ValueVector hierarchy (non-nullable) and those
* vectors have not yet been refactored/removed so moving things to
* the top class as of now is not a good idea.
*/
/**
* Get buffer that manages the validity (NULL or NON-NULL nature) of
* elements in the vector. Consider it as a buffer for internal bit vector
* data structure.
* @return buffer
*/
@Override
public ArrowBuf getValidityBuffer() {
return validityBuffer;
}
/**
* Get the buffer that stores the data for elements in the vector.
* @return buffer
*/
@Override
public ArrowBuf getDataBuffer() {
return valueBuffer;
}
/**
* buffer that stores the offsets for elements
* in the vector. This operation is not supported for fixed-width vectors.
* @return buffer
*/
@Override
public ArrowBuf getOffsetBuffer() {
return offsetBuffer;
}
/**
* Get the memory address of buffer that stores the offsets for elements
* in the vector.
* @return starting address of the buffer
*/
@Override
public long getOffsetBufferAddress() {
return offsetBuffer.memoryAddress();
}
/**
* Get the memory address of buffer that manages the validity
* (NULL or NON-NULL nature) of elements in the vector.
* @return starting address of the buffer
*/
@Override
public long getValidityBufferAddress() {
return validityBuffer.memoryAddress();
}
/**
* Get the memory address of buffer that stores the data for elements
* in the vector.
* @return starting address of the buffer
*/
@Override
public long getDataBufferAddress() {
return valueBuffer.memoryAddress();
}
/**
* Sets the desired value capacity for the vector. This function doesn't
* allocate any memory for the vector.
* @param valueCount desired number of elements in the vector
*/
@Override
public void setInitialCapacity(int valueCount) {
final long size = (long) valueCount * DEFAULT_RECORD_BYTE_COUNT;
checkDataBufferSize(size);
computeAndCheckOffsetsBufferSize(valueCount);
lastValueAllocationSizeInBytes = (int) size;
lastValueCapacity = valueCount;
}
/**
* Sets the desired value capacity for the vector. This function doesn't
* allocate any memory for the vector.
* @param valueCount desired number of elements in the vector
* @param density average number of bytes per variable width element
*/
@Override
public void setInitialCapacity(int valueCount, double density) {
long size = Math.max((long)(valueCount * density), 1L);
checkDataBufferSize(size);
computeAndCheckOffsetsBufferSize(valueCount);
lastValueAllocationSizeInBytes = (int) size;
lastValueCapacity = valueCount;
}
/**
* Get the density of this ListVector.
* @return density
*/
public double getDensity() {
if (valueCount == 0) {
return 0.0D;
}
final int startOffset = offsetBuffer.getInt(0);
final int endOffset = offsetBuffer.getInt(valueCount * OFFSET_WIDTH);
final double totalListSize = endOffset - startOffset;
return totalListSize / valueCount;
}
/**
* Get the current value capacity for the vector.
* @return number of elements that vector can hold.
*/
@Override
public int getValueCapacity() {
final int offsetValueCapacity = Math.max(getOffsetBufferValueCapacity() - 1, 0);
return Math.min(offsetValueCapacity, getValidityBufferValueCapacity());
}
private int getValidityBufferValueCapacity() {
return validityBuffer.capacity() * 8;
}
private int getOffsetBufferValueCapacity() {
return offsetBuffer.capacity() / OFFSET_WIDTH;
}
/**
* zero out the vector and the data in associated buffers.
*/
public void zeroVector() {
initValidityBuffer();
initOffsetBuffer();
valueBuffer.setZero(0, valueBuffer.capacity());
}
/* zero out the validity buffer */
private void initValidityBuffer() {
validityBuffer.setZero(0, validityBuffer.capacity());
}
/* zero out the offset buffer */
private void initOffsetBuffer() {
offsetBuffer.setZero(0, offsetBuffer.capacity());
}
/**
* Reset the vector to initial state. Same as {@link #zeroVector()}.
* Note that this method doesn't release any memory.
*/
public void reset() {
zeroVector();
lastSet = -1;
valueCount = 0;
}
/**
* Close the vector and release the associated buffers.
*/
@Override
public void close() {
clear();
}
/**
* Same as {@link #close()}.
*/
@Override
public void clear() {
validityBuffer = releaseBuffer(validityBuffer);
valueBuffer = releaseBuffer(valueBuffer);
offsetBuffer = releaseBuffer(offsetBuffer);
lastSet = -1;
valueCount = 0;
}
@Override
@Deprecated
public List<BufferBacked> getFieldInnerVectors() {
throw new UnsupportedOperationException("There are no inner vectors. Use getFieldBuffers");
}
/**
* Initialize the children in schema for this Field. This operation is a
* NO-OP for scalar types since they don't have any children.
* @param children the schema
* @throws IllegalArgumentException if children is a non-empty list for scalar types.
*/
@Override
public void initializeChildrenFromFields(List<Field> children) {
if (!children.isEmpty()) {
throw new IllegalArgumentException("primitive type vector can not have children");
}
}
/**
* Get the inner child vectors.
* @return list of child vectors for complex types, empty list for scalar vector types
*/
@Override
public List<FieldVector> getChildrenFromFields() {
return Collections.emptyList();
}
/**
* Load the buffers of this vector with provided source buffers.
* The caller manages the source buffers and populates them before invoking
* this method.
* @param fieldNode the fieldNode indicating the value count
* @param ownBuffers the buffers for this Field (own buffers only, children not included)
*/
@Override
public void loadFieldBuffers(ArrowFieldNode fieldNode, List<ArrowBuf> ownBuffers) {
ArrowBuf bitBuffer = ownBuffers.get(0);
ArrowBuf offBuffer = ownBuffers.get(1);
ArrowBuf dataBuffer = ownBuffers.get(2);
validityBuffer.getReferenceManager().release();
validityBuffer = BitVectorHelper.loadValidityBuffer(fieldNode, bitBuffer, allocator);
offsetBuffer.getReferenceManager().release();
offsetBuffer = offBuffer.getReferenceManager().retain(offBuffer, allocator);
valueBuffer.getReferenceManager().release();
valueBuffer = dataBuffer.getReferenceManager().retain(dataBuffer, allocator);
lastSet = fieldNode.getLength() - 1;
valueCount = fieldNode.getLength();
}
/**
* Get the buffers belonging to this vector.
* @return the inner buffers.
*/
public List<ArrowBuf> getFieldBuffers() {
List<ArrowBuf> result = new ArrayList<>(3);
setReaderAndWriterIndex();
result.add(validityBuffer);
result.add(offsetBuffer);
result.add(valueBuffer);
return result;
}
/**
* Set the reader and writer indexes for the inner buffers.
*/
private void setReaderAndWriterIndex() {
validityBuffer.readerIndex(0);
offsetBuffer.readerIndex(0);
valueBuffer.readerIndex(0);
if (valueCount == 0) {
validityBuffer.writerIndex(0);
offsetBuffer.writerIndex(0);
valueBuffer.writerIndex(0);
} else {
final int lastDataOffset = getStartOffset(valueCount);
validityBuffer.writerIndex(getValidityBufferSizeFromCount(valueCount));
offsetBuffer.writerIndex((valueCount + 1) * OFFSET_WIDTH);
valueBuffer.writerIndex(lastDataOffset);
}
}
/**
* Same as {@link #allocateNewSafe()}.
*/
@Override
public void allocateNew() {
allocateNew(lastValueAllocationSizeInBytes, lastValueCapacity);
}
/**
* Allocate memory for the vector. We internally use a default value count
* of 4096 to allocate memory for at least these many elements in the
* vector. See {@link #allocateNew(int, int)} for allocating memory for specific
* number of elements in the vector.
*
* @return false if memory allocation fails, true otherwise.
*/
@Override
public boolean allocateNewSafe() {
try {
allocateNew(lastValueAllocationSizeInBytes, lastValueCapacity);
return true;
} catch (Exception e) {
return false;
}
}
/**
* Allocate memory for the vector to support storing at least the provided number of
* elements in the vector. This method must be called prior to using the ValueVector.
*
* @param totalBytes desired total memory capacity
* @param valueCount the desired number of elements in the vector
* @throws org.apache.arrow.memory.OutOfMemoryException if memory allocation fails
*/
@Override
public void allocateNew(int totalBytes, int valueCount) {
assert totalBytes >= 0;
checkDataBufferSize(totalBytes);
computeAndCheckOffsetsBufferSize(valueCount);
/* we are doing a new allocation -- release the current buffers */
clear();
try {
allocateBytes(totalBytes, valueCount);
} catch (Exception e) {
clear();
throw e;
}
}
@Override
public void allocateNew(int valueCount) {
allocateNew(lastValueAllocationSizeInBytes, valueCount);
}
/* Check if the data buffer size is within bounds. */
private void checkDataBufferSize(long size) {
if (size > MAX_ALLOCATION_SIZE) {
throw new OversizedAllocationException("Memory required for vector " +
" is (" + size + "), which is more than max allowed (" + MAX_ALLOCATION_SIZE + ")");
}
}
/*
* Compute the buffer size required for 'valueCount' offsets and validity, and check if it's
* within bounds.
*/
private long computeAndCheckOffsetsBufferSize(int valueCount) {
/* to track the end offset of last data element in vector, we need
* an additional slot in offset buffer.
*/
final long size = computeCombinedBufferSize(valueCount + 1, OFFSET_WIDTH);
if (size > MAX_ALLOCATION_SIZE) {
throw new OversizedAllocationException("Memory required for vector capacity " +
valueCount +
" is (" + size + "), which is more than max allowed (" + MAX_ALLOCATION_SIZE + ")");
}
return size;
}
/* allocate the inner buffers */
private void allocateBytes(final int valueBufferSize, final int valueCount) {
/* allocate data buffer */
int curSize = valueBufferSize;
valueBuffer = allocator.buffer(curSize);
valueBuffer.readerIndex(0);
/* allocate offset buffer and validity buffer */
DataAndValidityBuffers buffers = allocFixedDataAndValidityBufs(valueCount + 1, OFFSET_WIDTH);
offsetBuffer = buffers.getDataBuf();
validityBuffer = buffers.getValidityBuf();
initOffsetBuffer();
initValidityBuffer();
lastValueCapacity = getValueCapacity();
lastValueAllocationSizeInBytes = valueBuffer.capacity();
}
/* allocate offset buffer */
private void allocateOffsetBuffer(final long size) {
final int curSize = (int) size;
offsetBuffer = allocator.buffer(curSize);
offsetBuffer.readerIndex(0);
initOffsetBuffer();
}
/* allocate validity buffer */
private void allocateValidityBuffer(final long size) {
final int curSize = (int) size;
validityBuffer = allocator.buffer(curSize);
validityBuffer.readerIndex(0);
initValidityBuffer();
}
/**
* Resize the vector to increase the capacity. The internal behavior is to
* double the current value capacity.
*/
public void reAlloc() {
reallocDataBuffer();
reallocValidityAndOffsetBuffers();
}
/**
* Reallocate the data buffer. Data Buffer stores the actual data for
* VARCHAR or VARBINARY elements in the vector. The behavior is to double
* the size of buffer.
* @throws OversizedAllocationException if the desired new size is more than
* max allowed
* @throws OutOfMemoryException if the internal memory allocation fails
*/
public void reallocDataBuffer() {
final int currentBufferCapacity = valueBuffer.capacity();
long newAllocationSize = currentBufferCapacity * 2;
if (newAllocationSize == 0) {
if (lastValueAllocationSizeInBytes > 0) {
newAllocationSize = lastValueAllocationSizeInBytes;
} else {
newAllocationSize = INITIAL_BYTE_COUNT * 2;
}
}
newAllocationSize = BaseAllocator.nextPowerOfTwo(newAllocationSize);
assert newAllocationSize >= 1;
checkDataBufferSize(newAllocationSize);
final ArrowBuf newBuf = allocator.buffer((int) newAllocationSize);
newBuf.setBytes(0, valueBuffer, 0, currentBufferCapacity);
valueBuffer.getReferenceManager().release();
valueBuffer = newBuf;
lastValueAllocationSizeInBytes = valueBuffer.capacity();
}
/**
* Reallocate the validity and offset buffers for this vector. Validity
* buffer is used to track the NULL or NON-NULL nature of elements in
* the vector and offset buffer is used to store the lengths of variable
* width elements in the vector.
*
* <p>Note that data buffer for variable length vectors moves independent
* of the companion validity and offset buffers. This is in
* contrast to what we have for fixed width vectors.
*
* <p>So even though we may have setup an initial capacity of 1024
* elements in the vector, it is quite possible
* that we need to reAlloc() the data buffer when we are setting
* the 5th element in the vector simply because previous
* variable length elements have exhausted the buffer capacity.
* However, we really don't need to reAlloc() validity and
* offset buffers until we try to set the 1025th element
* This is why we do a separate check for safe methods to
* determine which buffer needs reallocation.
* @throws OversizedAllocationException if the desired new size is more than
* max allowed
* @throws OutOfMemoryException if the internal memory allocation fails
*/
public void reallocValidityAndOffsetBuffers() {
int targetOffsetCount = (offsetBuffer.capacity() / OFFSET_WIDTH) * 2;
if (targetOffsetCount == 0) {
if (lastValueCapacity > 0) {
targetOffsetCount = (lastValueCapacity + 1);
} else {
targetOffsetCount = 2 * (INITIAL_VALUE_ALLOCATION + 1);
}
}
computeAndCheckOffsetsBufferSize(targetOffsetCount);
DataAndValidityBuffers buffers = allocFixedDataAndValidityBufs(targetOffsetCount, OFFSET_WIDTH);
final ArrowBuf newOffsetBuffer = buffers.getDataBuf();
newOffsetBuffer.setBytes(0, offsetBuffer, 0, offsetBuffer.capacity());
newOffsetBuffer.setZero(offsetBuffer.capacity(), newOffsetBuffer.capacity() - offsetBuffer.capacity());
offsetBuffer.getReferenceManager().release();
offsetBuffer = newOffsetBuffer;
final ArrowBuf newValidityBuffer = buffers.getValidityBuf();
newValidityBuffer.setBytes(0, validityBuffer, 0, validityBuffer.capacity());
newValidityBuffer.setZero(validityBuffer.capacity(), newValidityBuffer.capacity() - validityBuffer.capacity());
validityBuffer.getReferenceManager().release();
validityBuffer = newValidityBuffer;
lastValueCapacity = getValueCapacity();
}
/**
* Get the size (number of bytes) of underlying data buffer.
* @return number of bytes in the data buffer
*/
@Override
public int getByteCapacity() {
return valueBuffer.capacity();
}
@Override
public int sizeOfValueBuffer() {
if (valueCount == 0) {
return 0;
}
return offsetBuffer.getInt(valueCount * OFFSET_WIDTH);
}
/**
* Get the size (number of bytes) of underlying buffers used by this
* vector.
* @return size of underlying buffers.
*/
@Override
public int getBufferSize() {
return getBufferSizeFor(this.valueCount);
}
/**
* Get the potential buffer size for a particular number of records.
* @param valueCount desired number of elements in the vector
* @return estimated size of underlying buffers if the vector holds
* a given number of elements
*/
@Override
public int getBufferSizeFor(final int valueCount) {
if (valueCount == 0) {
return 0;
}
final int validityBufferSize = getValidityBufferSizeFromCount(valueCount);
final int offsetBufferSize = (valueCount + 1) * OFFSET_WIDTH;
/* get the end offset for this valueCount */
final int dataBufferSize = offsetBuffer.getInt(valueCount * OFFSET_WIDTH);
return validityBufferSize + offsetBufferSize + dataBufferSize;
}
/**
* Get information about how this field is materialized.
* @return the field corresponding to this vector
*/
@Override
public Field getField() {
return field;
}
/**
* Return the underlying buffers associated with this vector. Note that this doesn't
* impact the reference counts for this buffer so it only should be used for in-context
* access. Also note that this buffer changes regularly thus
* external classes shouldn't hold a reference to it (unless they change it).
*
* @param clear Whether to clear vector before returning; the buffers will still be refcounted
* but the returned array will be the only reference to them
* @return The underlying {@link io.netty.buffer.ArrowBuf buffers} that is used by this
* vector instance.
*/
@Override
public ArrowBuf[] getBuffers(boolean clear) {
final ArrowBuf[] buffers;
setReaderAndWriterIndex();
if (getBufferSize() == 0) {
buffers = new ArrowBuf[0];
} else {
buffers = new ArrowBuf[3];
buffers[0] = validityBuffer;
buffers[1] = offsetBuffer;
buffers[2] = valueBuffer;
}
if (clear) {
for (final ArrowBuf buffer : buffers) {
buffer.getReferenceManager().retain();
}
clear();
}
return buffers;
}
/**
* Construct a transfer pair of this vector and another vector of same type.
* @param ref name of the target vector
* @param allocator allocator for the target vector
* @param callBack not used
* @return TransferPair
*/
@Override
public TransferPair getTransferPair(String ref, BufferAllocator allocator, CallBack callBack) {
return getTransferPair(ref, allocator);
}
/**
* Construct a transfer pair of this vector and another vector of same type.
* @param allocator allocator for the target vector
* @return TransferPair
*/
@Override
public TransferPair getTransferPair(BufferAllocator allocator) {
return getTransferPair(getName(), allocator);
}
/**
* Construct a transfer pair of this vector and another vector of same type.
* @param ref name of the target vector
* @param allocator allocator for the target vector
* @return TransferPair
*/
public abstract TransferPair getTransferPair(String ref, BufferAllocator allocator);
/**
* Transfer this vector'data to another vector. The memory associated
* with this vector is transferred to the allocator of target vector
* for accounting and management purposes.
* @param target destination vector for transfer
*/
public void transferTo(BaseVariableWidthVector target) {
compareTypes(target, "transferTo");
target.clear();
target.validityBuffer = transferBuffer(validityBuffer, target.allocator);
target.valueBuffer = transferBuffer(valueBuffer, target.allocator);
target.offsetBuffer = transferBuffer(offsetBuffer, target.allocator);
target.setLastSet(this.lastSet);
if (this.valueCount > 0) {
target.setValueCount(this.valueCount);
}
clear();
}
/**
* Slice this vector at desired index and length and transfer the
* corresponding data to the target vector.
* @param startIndex start position of the split in source vector.
* @param length length of the split.
* @param target destination vector
*/
public void splitAndTransferTo(int startIndex, int length,
BaseVariableWidthVector target) {
compareTypes(target, "splitAndTransferTo");
target.clear();
splitAndTransferValidityBuffer(startIndex, length, target);
splitAndTransferOffsetBuffer(startIndex, length, target);
target.setLastSet(length - 1);
if (length > 0) {
target.setValueCount(length);
}
}
/**
* Transfer the offsets along with data. Unlike the data buffer, we cannot simply
* slice the offset buffer for split and transfer. The reason is that offsets
* in the target vector have to be adjusted and made relative to the staring
* offset in source vector from the start index of split. This is why, we
* need to explicitly allocate the offset buffer and set the adjusted offsets
* in the target vector.
*/
private void splitAndTransferOffsetBuffer(int startIndex, int length, BaseVariableWidthVector target) {
final int start = offsetBuffer.getInt(startIndex * OFFSET_WIDTH);
final int end = offsetBuffer.getInt((startIndex + length) * OFFSET_WIDTH);
final int dataLength = end - start;
target.allocateOffsetBuffer((length + 1) * OFFSET_WIDTH);
for (int i = 0; i < length + 1; i++) {
final int relativeSourceOffset = offsetBuffer.getInt((startIndex + i) * OFFSET_WIDTH) - start;
target.offsetBuffer.setInt(i * OFFSET_WIDTH, relativeSourceOffset);
}
final ArrowBuf slicedBuffer = valueBuffer.slice(start, dataLength);
target.valueBuffer = transferBuffer(slicedBuffer, target.allocator);
}
/*
* Transfer the validity.
*/
private void splitAndTransferValidityBuffer(int startIndex, int length,
BaseVariableWidthVector target) {
assert startIndex + length <= valueCount;
int firstByteSource = BitVectorHelper.byteIndex(startIndex);
int lastByteSource = BitVectorHelper.byteIndex(valueCount - 1);
int byteSizeTarget = getValidityBufferSizeFromCount(length);
int offset = startIndex % 8;
if (length > 0) {
if (offset == 0) {
// slice
if (target.validityBuffer != null) {
target.validityBuffer.getReferenceManager().release();
}
target.validityBuffer = validityBuffer.slice(firstByteSource, byteSizeTarget);
target.validityBuffer.getReferenceManager().retain();
} else {
/* Copy data
* When the first bit starts from the middle of a byte (offset != 0),
* copy data from src BitVector.
* Each byte in the target is composed by a part in i-th byte,
* another part in (i+1)-th byte.
*/
target.allocateValidityBuffer(byteSizeTarget);
for (int i = 0; i < byteSizeTarget - 1; i++) {
byte b1 = BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer, firstByteSource + i, offset);
byte b2 = BitVectorHelper.getBitsFromNextByte(this.validityBuffer, firstByteSource + i + 1, offset);
target.validityBuffer.setByte(i, (b1 + b2));
}
/* Copying the last piece is done in the following manner:
* if the source vector has 1 or more bytes remaining, we copy
* the last piece as a byte formed by shifting data
* from the current byte and the next byte.
*
* if the source vector has no more bytes remaining
* (we are at the last byte), we copy the last piece as a byte
* by shifting data from the current byte.
*/
if ((firstByteSource + byteSizeTarget - 1) < lastByteSource) {
byte b1 = BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer,
firstByteSource + byteSizeTarget - 1, offset);
byte b2 = BitVectorHelper.getBitsFromNextByte(this.validityBuffer,
firstByteSource + byteSizeTarget, offset);
target.validityBuffer.setByte(byteSizeTarget - 1, b1 + b2);
} else {
byte b1 = BitVectorHelper.getBitsFromCurrentByte(this.validityBuffer,
firstByteSource + byteSizeTarget - 1, offset);
target.validityBuffer.setByte(byteSizeTarget - 1, b1);
}
}
}
}
/*----------------------------------------------------------------*
| |
| common getters and setters |
| |
*----------------------------------------------------------------*/
/**
* Get the number of elements that are null in the vector.
*
* @return the number of null elements.
*/
public int getNullCount() {
return BitVectorHelper.getNullCount(validityBuffer, valueCount);
}
/**
* Check if the given index is within the current value capacity
* of the vector.
*
* @param index position to check
* @return true if index is within the current value capacity
*/
public boolean isSafe(int index) {
return index < getValueCapacity();
}
/**
* Check if element at given index is null.
*
* @param index position of element
* @return true if element at given index is null
*/
public boolean isNull(int index) {
return (isSet(index) == 0);
}
/**
* Same as {@link #isNull(int)}.
*
* @param index position of element
* @return 1 if element at given index is not null, 0 otherwise
*/
public int isSet(int index) {
final int byteIndex = index >> 3;
final byte b = validityBuffer.getByte(byteIndex);
final int bitIndex = index & 7;
return (b >> bitIndex) & 0x01;
}
/**
* Get the value count of vector. This will always be zero unless
* setValueCount(int) has been called prior to calling this.
*
* @return valueCount for the vector
*/
public int getValueCount() {
return valueCount;
}
/**
* Sets the value count for the vector.
*
* @param valueCount value count
*/
public void setValueCount(int valueCount) {
assert valueCount >= 0;
this.valueCount = valueCount;
while (valueCount > getValueCapacity()) {
reallocValidityAndOffsetBuffers();
}
fillHoles(valueCount);
lastSet = valueCount - 1;
setReaderAndWriterIndex();
}
/**
* Create holes in the vector upto the given index (exclusive).
* Holes will be created from the current last set position in
* the vector.
*
* @param index target index
*/
public void fillEmpties(int index) {
handleSafe(index, emptyByteArray.length);
fillHoles(index);
lastSet = index - 1;
}
/**
* Set the index of last non-null element in the vector.
* It is important to call this method with appropriate value
* before calling {@link #setValueCount(int)}.
*
* @param value desired index of last non-null element.
*/
public void setLastSet(int value) {
lastSet = value;
}
/**
* Get the index of last non-null element in the vector.
*
* @return index of the last non-null element
*/
public int getLastSet() {
return lastSet;
}
/**
* Get the starting position (offset) in the data stream for a given
* element in the vector.
*
* @param index position of the element in the vector
* @return starting offset for the element
*/
public long getStartEnd(int index) {
return offsetBuffer.getLong(index * OFFSET_WIDTH);
}
/**
* Mark the particular position in the vector as non-null.
*
* @param index position of the element.
*/
@Override
public void setIndexDefined(int index) {
while (index >= getValidityBufferValueCapacity()) {
reallocValidityAndOffsetBuffers();
}
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
}
/**
* Sets the value length for an element.
*
* @param index position of the element to set
* @param length length of the element
*/
public void setValueLengthSafe(int index, int length) {
assert index >= 0;
handleSafe(index, length);
fillHoles(index);
final int startOffset = getStartOffset(index);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
lastSet = index;
}
/**
* Get the variable length element at specified index as Text.
*
* @param index position of element to get
* @return greater than 0 length for non-null element, 0 otherwise
*/
public int getValueLength(int index) {
assert index >= 0;
if (isSet(index) == 0) {
return 0;
}
final int startOffset = getStartOffset(index);
final int dataLength =
offsetBuffer.getInt((index + 1) * OFFSET_WIDTH) - startOffset;
return dataLength;
}
/**
* Set the variable length element at the specified index to the supplied
* byte array. This is same as using {@link #set(int, byte[], int, int)}
* with start as 0 and length as value.length
*
* @param index position of the element to set
* @param value array of bytes to write
*/
public void set(int index, byte[] value) {
assert index >= 0;
fillHoles(index);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
setBytes(index, value, 0, value.length);
lastSet = index;
}
/**
* Same as {@link #set(int, byte[])} except that it handles the
* case where index and length of new element are beyond the existing
* capacity of the vector.
*
* @param index position of the element to set
* @param value array of bytes to write
*/
public void setSafe(int index, byte[] value) {
assert index >= 0;
fillEmpties(index);
handleSafe(index, value.length);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
setBytes(index, value, 0, value.length);
lastSet = index;
}
/**
* Set the variable length element at the specified index to the supplied
* byte array.
*
* @param index position of the element to set
* @param value array of bytes to write
* @param start start index in array of bytes
* @param length length of data in array of bytes
*/
public void set(int index, byte[] value, int start, int length) {
assert index >= 0;
fillHoles(index);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
setBytes(index, value, start, length);
lastSet = index;
}
/**
* Same as {@link #set(int, byte[], int, int)} except that it handles the
* case where index and length of new element are beyond the existing
* capacity of the vector.
*
* @param index position of the element to set
* @param value array of bytes to write
* @param start start index in array of bytes
* @param length length of data in array of bytes
*/
public void setSafe(int index, byte[] value, int start, int length) {
assert index >= 0;
fillEmpties(index);
handleSafe(index, length);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
setBytes(index, value, start, length);
lastSet = index;
}
/**
* Set the variable length element at the specified index to the
* content in supplied ByteBuffer.
*
* @param index position of the element to set
* @param value ByteBuffer with data
* @param start start index in ByteBuffer
* @param length length of data in ByteBuffer
*/
public void set(int index, ByteBuffer value, int start, int length) {
assert index >= 0;
fillHoles(index);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
final int startOffset = getStartOffset(index);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
valueBuffer.setBytes(startOffset, value, start, length);
lastSet = index;
}
/**
* Same as {@link #set(int, ByteBuffer, int, int)} except that it handles the
* case where index and length of new element are beyond the existing
* capacity of the vector.
*
* @param index position of the element to set
* @param value ByteBuffer with data
* @param start start index in ByteBuffer
* @param length length of data in ByteBuffer
*/
public void setSafe(int index, ByteBuffer value, int start, int length) {
assert index >= 0;
fillEmpties(index);
handleSafe(index, length);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
final int startOffset = getStartOffset(index);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
valueBuffer.setBytes(startOffset, value, start, length);
lastSet = index;
}
/**
* Set the element at the given index to null.
*
* @param index position of element
*/
public void setNull(int index) {
while (index >= getValidityBufferValueCapacity()) {
reallocValidityAndOffsetBuffers();
}
BitVectorHelper.setValidityBit(validityBuffer, index, 0);
}
/**
* Store the given value at a particular position in the vector. isSet indicates
* whether the value is NULL or not.
* @param index position of the new value
* @param isSet 0 for NULL value, 1 otherwise
* @param start start position of data in buffer
* @param end end position of data in buffer
* @param buffer data buffer containing the variable width element to be stored
* in the vector
*/
public void set(int index, int isSet, int start, int end, ArrowBuf buffer) {
assert index >= 0;
final int dataLength = end - start;
fillHoles(index);
BitVectorHelper.setValidityBit(validityBuffer, index, isSet);
final int startOffset = offsetBuffer.getInt(index * OFFSET_WIDTH);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + dataLength);
valueBuffer.setBytes(startOffset, buffer, start, dataLength);
lastSet = index;
}
/**
* Same as {@link #set(int, int, int, int, ArrowBuf)} except that it handles the case
* when index is greater than or equal to current value capacity of the
* vector.
* @param index position of the new value
* @param isSet 0 for NULL value, 1 otherwise
* @param start start position of data in buffer
* @param end end position of data in buffer
* @param buffer data buffer containing the variable width element to be stored
* in the vector
*/
public void setSafe(int index, int isSet, int start, int end, ArrowBuf buffer) {
assert index >= 0;
final int dataLength = end - start;
fillEmpties(index);
handleSafe(index, dataLength);
BitVectorHelper.setValidityBit(validityBuffer, index, isSet);
final int startOffset = offsetBuffer.getInt(index * OFFSET_WIDTH);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + dataLength);
valueBuffer.setBytes(startOffset, buffer, start, dataLength);
lastSet = index;
}
/**
* Store the given value at a particular position in the vector. isSet indicates
* whether the value is NULL or not.
* @param index position of the new value
* @param start start position of data in buffer
* @param length length of data in buffer
* @param buffer data buffer containing the variable width element to be stored
* in the vector
*/
public void set(int index, int start, int length, ArrowBuf buffer) {
assert index >= 0;
fillHoles(index);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
final int startOffset = offsetBuffer.getInt(index * OFFSET_WIDTH);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
final ArrowBuf bb = buffer.slice(start, length);
valueBuffer.setBytes(startOffset, bb);
lastSet = index;
}
/**
* Same as {@link #set(int, int, int, int, ArrowBuf)} except that it handles the case
* when index is greater than or equal to current value capacity of the
* vector.
* @param index position of the new value
* @param start start position of data in buffer
* @param length length of data in buffer
* @param buffer data buffer containing the variable width element to be stored
* in the vector
*/
public void setSafe(int index, int start, int length, ArrowBuf buffer) {
assert index >= 0;
fillEmpties(index);
handleSafe(index, length);
BitVectorHelper.setValidityBitToOne(validityBuffer, index);
final int startOffset = offsetBuffer.getInt(index * OFFSET_WIDTH);
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
final ArrowBuf bb = buffer.slice(start, length);
valueBuffer.setBytes(startOffset, bb);
lastSet = index;
}
/*----------------------------------------------------------------*
| |
| helper methods for setters |
| |
*----------------------------------------------------------------*/
protected final void fillHoles(int index) {
for (int i = lastSet + 1; i < index; i++) {
setBytes(i, emptyByteArray, 0, emptyByteArray.length);
}
lastSet = index - 1;
}
protected final void setBytes(int index, byte[] value, int start, int length) {
/* end offset of current last element in the vector. this will
* be the start offset of new element we are trying to store.
*/
final int startOffset = getStartOffset(index);
/* set new end offset */
offsetBuffer.setInt((index + 1) * OFFSET_WIDTH, startOffset + length);
/* store the var length data in value buffer */
valueBuffer.setBytes(startOffset, value, start, length);
}
/**
* Gets the starting offset of a record, given its index.
* This method is deprecated. Please use {@link BaseVariableWidthVector#getStartOffset(int)} instead.
* @param index index of the record.
* @return the starting offset of the record.
*/
@Deprecated
protected final int getstartOffset(int index) {
return getStartOffset(index);
}
protected final int getStartOffset(int index) {
return offsetBuffer.getInt(index * OFFSET_WIDTH);
}
protected final void handleSafe(int index, int dataLength) {
/*
* IMPORTANT:
* value buffer for variable length vectors moves independent
* of the companion validity and offset buffers. This is in
* contrast to what we have for fixed width vectors.
*
* Here there is no concept of getValueCapacity() in the
* data stream. getValueCapacity() is applicable only to validity
* and offset buffers.
*
* So even though we may have setup an initial capacity of 1024
* elements in the vector, it is quite possible
* that we need to reAlloc() the data buffer when we are setting
* the 5th element in the vector simply because previous
* variable length elements have exhausted the buffer capacity.
* However, we really don't need to reAlloc() validity and
* offset buffers until we try to set the 1025th element
* This is why we do a separate check for safe methods to
* determine which buffer needs reallocation.
*/
while (index >= getValueCapacity()) {
reallocValidityAndOffsetBuffers();
}
final int startOffset = getStartOffset(index);
while (valueBuffer.capacity() < (startOffset + dataLength)) {
reallocDataBuffer();
}
}
/**
* Method used by Json Writer to read a variable width element from
* the variable width vector and write to Json.
*
* <p>This method should not be used externally.
*
* @param data buffer storing the variable width vector elements
* @param offset buffer storing the offsets of variable width vector elements
* @param index position of the element in the vector
* @return array of bytes
*/
public static byte[] get(final ArrowBuf data, final ArrowBuf offset, int index) {
final int currentStartOffset = offset.getInt(index * OFFSET_WIDTH);
final int dataLength =
offset.getInt((index + 1) * OFFSET_WIDTH) - currentStartOffset;
final byte[] result = new byte[dataLength];
data.getBytes(currentStartOffset, result, 0, dataLength);
return result;
}
/**
* Method used by Json Reader to explicitly set the offsets of the variable
* width vector data. The method takes care of allocating the memory for
* offsets if the caller hasn't done so.
*
* <p>This method should not be used externally.
*
* @param buffer ArrowBuf to store offsets for variable width elements
* @param allocator memory allocator
* @param valueCount number of elements
* @param index position of the element
* @param value offset of the element
* @return buffer holding the offsets
*/
public static ArrowBuf set(ArrowBuf buffer, BufferAllocator allocator,
int valueCount, int index, int value) {
if (buffer == null) {
buffer = allocator.buffer(valueCount * OFFSET_WIDTH);
}
buffer.setInt(index * OFFSET_WIDTH, value);
if (index == (valueCount - 1)) {
buffer.writerIndex(valueCount * OFFSET_WIDTH);
}
return buffer;
}
/**
* Copy a cell value from a particular index in source vector to a particular
* position in this vector.
*
* @param fromIndex position to copy from in source vector
* @param thisIndex position to copy to in this vector
* @param from source vector
*/
@Override
public void copyFrom(int fromIndex, int thisIndex, ValueVector from) {
Preconditions.checkArgument(this.getMinorType() == from.getMinorType());
if (from.isNull(fromIndex)) {
fillHoles(thisIndex);
BitVectorHelper.setValidityBit(this.validityBuffer, thisIndex, 0);
final int copyStart = offsetBuffer.getInt(thisIndex * OFFSET_WIDTH);
offsetBuffer.setInt((thisIndex + 1) * OFFSET_WIDTH, copyStart);
} else {
final int start = from.getOffsetBuffer().getInt(fromIndex * OFFSET_WIDTH);
final int end = from.getOffsetBuffer().getInt((fromIndex + 1) * OFFSET_WIDTH);
final int length = end - start;
fillHoles(thisIndex);
BitVectorHelper.setValidityBit(this.validityBuffer, thisIndex, 1);
final int copyStart = offsetBuffer.getInt(thisIndex * OFFSET_WIDTH);
from.getDataBuffer().getBytes(start, this.valueBuffer, copyStart, length);
offsetBuffer.setInt((thisIndex + 1) * OFFSET_WIDTH, copyStart + length);
}
lastSet = thisIndex;
}
/**
* Same as {@link #copyFrom(int, int, ValueVector)} except that
* it handles the case when the capacity of the vector needs to be expanded
* before copy.
*
* @param fromIndex position to copy from in source vector
* @param thisIndex position to copy to in this vector
* @param from source vector
*/
@Override
public void copyFromSafe(int fromIndex, int thisIndex, ValueVector from) {
Preconditions.checkArgument(this.getMinorType() == from.getMinorType());
if (from.isNull(fromIndex)) {
handleSafe(thisIndex, 0);
fillHoles(thisIndex);
BitVectorHelper.setValidityBit(this.validityBuffer, thisIndex, 0);
final int copyStart = offsetBuffer.getInt(thisIndex * OFFSET_WIDTH);
offsetBuffer.setInt((thisIndex + 1) * OFFSET_WIDTH, copyStart);
} else {
final int start = from.getOffsetBuffer().getInt(fromIndex * OFFSET_WIDTH);
final int end = from.getOffsetBuffer().getInt((fromIndex + 1) * OFFSET_WIDTH);
final int length = end - start;
handleSafe(thisIndex, length);
fillHoles(thisIndex);
BitVectorHelper.setValidityBit(this.validityBuffer, thisIndex, 1);
final int copyStart = offsetBuffer.getInt(thisIndex * OFFSET_WIDTH);
from.getDataBuffer().getBytes(start, this.valueBuffer, copyStart, length);
offsetBuffer.setInt((thisIndex + 1) * OFFSET_WIDTH, copyStart + length);
}
lastSet = thisIndex;
}
@Override
public ArrowBufPointer getDataPointer(int index) {
return getDataPointer(index, new ArrowBufPointer());
}
@Override
public ArrowBufPointer getDataPointer(int index, ArrowBufPointer reuse) {
if (isNull(index)) {
reuse.set(null, 0, 0);
} else {
int offset = offsetBuffer.getInt(index * OFFSET_WIDTH);
int length = offsetBuffer.getInt((index + 1) * OFFSET_WIDTH) - offset;
reuse.set(valueBuffer, offset, length);
}
return reuse;
}
@Override
public int hashCode(int index) {
final int start = getStartOffset(index);
final int end = getStartOffset(index + 1);
return ByteFunctionHelpers.hash(this.getDataBuffer(), start, end);
}
@Override
public <OUT, IN> OUT accept(VectorVisitor<OUT, IN> visitor, IN value) {
return visitor.visit(this, value);
}
}