blob: 5aa4e337d837d59dae47234b09cdeea1d00ea1d4 [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.ignite.internal.schema.row;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.util.BitSet;
import java.util.UUID;
import org.apache.ignite.internal.schema.BinaryRow;
import org.apache.ignite.internal.schema.Column;
import org.apache.ignite.internal.schema.Columns;
import org.apache.ignite.internal.schema.InvalidTypeException;
import org.apache.ignite.internal.schema.NativeTypeSpec;
import org.apache.ignite.internal.schema.SchemaDescriptor;
/**
* Schema-aware row.
* <p>
* The class contains non-generic methods to read boxed and unboxed primitives based on the schema column types.
* Any type conversions and coercions should be implemented outside the row by the key-value or query runtime.
* When a non-boxed primitive is read from a null column value, it is converted to the primitive type default value.
*/
public class Row implements BinaryRow {
/** Schema descriptor. */
protected final SchemaDescriptor schema;
/** Binary row. */
private final BinaryRow row;
/**
* Constructor.
*
* @param schema Schema.
* @param row Binary row representation.
*/
public Row(SchemaDescriptor schema, BinaryRow row) {
this.row = row;
this.schema = schema;
}
/**
* @return Row schema.
*/
public SchemaDescriptor rowSchema() {
return schema;
}
/**
* @return {@code True} if row has non-null value, {@code false} otherwise.
*/
@Override public boolean hasValue() {
return row.hasValue();
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public byte byteValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT8);
return off < 0 ? 0 : readByte(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Byte byteValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT8);
return off < 0 ? null : readByte(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public short shortValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT16);
return off < 0 ? 0 : readShort(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Short shortValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT16);
return off < 0 ? null : readShort(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public int intValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT32);
return off < 0 ? 0 : readInteger(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Integer intValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT32);
return off < 0 ? null : readInteger(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public long longValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT64);
return off < 0 ? 0 : readLong(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Long longValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.INT64);
return off < 0 ? null : readLong(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public float floatValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.FLOAT);
return off < 0 ? 0.f : readFloat(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Float floatValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.FLOAT);
return off < 0 ? null : readFloat(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public double doubleValue(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.DOUBLE);
return off < 0 ? 0.d : readDouble(offset(off));
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public Double doubleValueBoxed(int col) throws InvalidTypeException {
long off = findColumn(col, NativeTypeSpec.DOUBLE);
return off < 0 ? null : readDouble(offset(off));
}
/**
* Reads value from specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public BigDecimal decimalValue(int col) throws InvalidTypeException {
// TODO: IGNITE-13668 decimal support
return null;
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public String stringValue(int col) throws InvalidTypeException {
long offLen = findColumn(col, NativeTypeSpec.STRING);
if (offLen < 0)
return null;
int off = offset(offLen);
int len = length(offLen);
return readString(off, len);
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public byte[] bytesValue(int col) throws InvalidTypeException {
long offLen = findColumn(col, NativeTypeSpec.BYTES);
if (offLen < 0)
return null;
int off = offset(offLen);
int len = length(offLen);
return readBytes(off, len);
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public UUID uuidValue(int col) throws InvalidTypeException {
long found = findColumn(col, NativeTypeSpec.UUID);
if (found < 0)
return null;
int off = offset(found);
long lsb = readLong(off);
long msb = readLong(off + 8);
return new UUID(msb, lsb);
}
/**
* Reads value for specified column.
*
* @param col Column index.
* @return Column value.
* @throws InvalidTypeException If actual column type does not match the requested column type.
*/
public BitSet bitmaskValue(int col) throws InvalidTypeException {
long offLen = findColumn(col, NativeTypeSpec.BITMASK);
if (offLen < 0)
return null;
int off = offset(offLen);
int len = columnLength(col);
return BitSet.valueOf(readBytes(off, len));
}
/**
* @return Row flags.
*/
private boolean hasFlag(int flag) {
return ((readShort(FLAGS_FIELD_OFFSET) & flag)) != 0;
}
/**
* Gets the column offset and length encoded into a single 8-byte value (4 least significant bytes encoding the
* offset from the beginning of the row and 4 most significant bytes encoding the field length for varlength
* columns). The offset and length should be extracted using {@link #offset(long)} and {@link #length(long)}
* methods.
* Will also validate that the actual column type matches the requested column type, throwing
* {@link InvalidTypeException} if the types do not match.
*
* @param colIdx Column index.
* @param type Expected column type.
* @return Encoded offset + length of the column.
* @see #offset(long)
* @see #length(long)
* @see InvalidTypeException If actual column type does not match the requested column type.
*/
protected long findColumn(int colIdx, NativeTypeSpec type) throws InvalidTypeException {
// Get base offset (key start or value start) for the given column.
boolean isKeyCol = schema.isKeyColumn(colIdx);
Columns cols;
int chunkBaseOff;
int flags;
if (isKeyCol) {
cols = schema.keyColumns();
chunkBaseOff = KEY_CHUNK_OFFSET;
flags = keyFlags();
}
else { // Adjust the column index according to the number of key columns.
if (!hasValue())
throw new IllegalStateException("Row has no value.");
colIdx -= schema.keyColumns().length();
cols = schema.valueColumns();
chunkBaseOff = KEY_CHUNK_OFFSET + readInteger(KEY_CHUNK_OFFSET);
flags = valueFlags();
}
if (cols.column(colIdx).type().spec() != type)
throw new InvalidTypeException("Invalid column type requested [requested=" + type +
", column=" + cols.column(colIdx) + ']');
int nullMapLen = (flags & VarTableFormat.OMIT_NULL_MAP_FLAG) == 0 ? cols.nullMapSize() : 0;
VarTableFormat format = (flags & VarTableFormat.OMIT_VARTBL_FLAG) == 0 ? VarTableFormat.fromFlags(flags) : null;
if (nullMapLen > 0 && isNull(chunkBaseOff, colIdx))
return -1;
int dataOffset = varTableOffset(chunkBaseOff, nullMapLen);
if (format != null)
dataOffset += format.vartableLength(format.readVartableSize(row, dataOffset));
return type.fixedLength() ?
fixedSizeColumnOffset(chunkBaseOff, dataOffset, cols, colIdx, nullMapLen > 0) :
varlenColumnOffsetAndLength(chunkBaseOff, dataOffset, cols, colIdx, nullMapLen, format);
}
/**
* Calculates the offset of the fixed-size column with the given index in the row. It essentially folds the null-map
* with the column lengths to calculate the size of non-null columns preceding the requested column.
*
* @param chunkBaseOff Chunk base offset.
* @param dataOffset Chunk data offset.
* @param cols Columns chunk.
* @param idx Column index in the chunk.
* @param hasNullmap {@code true} if chunk has null-map, {@code false} otherwise.
* @return Encoded offset (from the row start) of the requested fixlen column.
*/
int fixedSizeColumnOffset(int chunkBaseOff, int dataOffset, Columns cols, int idx, boolean hasNullmap) {
int colOff = 0;
// Calculate fixlen column offset.
int colByteIdx = idx >> 3; // Equivalent expression for: idx / 8
// Set bits starting from posInByte, inclusive, up to either the end of the byte or the last column index, inclusive
int startBit = idx & 7; // Equivalent expression for: idx % 8
int endBit = (colByteIdx == (cols.length() + 7) >> 3 - 1) /* last byte */ ?
((cols.numberOfFixsizeColumns() - 1) & 7) : 7; // Equivalent expression for: (expr) % 8
int mask = (0xFF >> (7 - endBit)) & (0xFF << startBit);
if (hasNullmap) {
// Fold offset based on the whole map bytes in the schema
for (int i = 0; i < colByteIdx; i++)
colOff += cols.foldFixedLength(i, Byte.toUnsignedInt(row.readByte(nullMapOffset(chunkBaseOff) + i)));
colOff += cols.foldFixedLength(colByteIdx, Byte.toUnsignedInt(row.readByte(nullMapOffset(chunkBaseOff) + colByteIdx)) | mask);
}
else {
for (int i = 0; i < colByteIdx; i++)
colOff += cols.foldFixedLength(i, 0);
colOff += cols.foldFixedLength(colByteIdx, mask);
}
return dataOffset + colOff;
}
/**
* Calculates the offset and length of varlen column. First, it calculates the number of non-null columns
* preceding the requested column by folding the null-map bits. This number is used to adjust the column index
* and find the corresponding entry in the varlen table. The length of the column is calculated either by
* subtracting two adjacent varlen table offsets, or by subtracting the last varlen table offset from the chunk
* length.
* <p>
* Note: Offset for the very fisrt varlen is skipped in vartable and calculated from fixlen columns sizes.
*
* @param baseOff Chunk base offset.
* @param dataOff Chunk data offset.
* @param cols Columns chunk.
* @param idx Column index in the chunk.
* @param nullMapLen Null-map length or {@code 0} if null-map is omitted.
* @param format Vartable format helper or {@code null} if vartable is omitted.
* @return Encoded offset (from the row start) and length of the column with the given index.
*/
long varlenColumnOffsetAndLength(
int baseOff,
int dataOff,
Columns cols,
int idx,
int nullMapLen,
VarTableFormat format
) {
assert cols.hasVarlengthColumns() && cols.firstVarlengthColumn() <= idx : "Invalid varlen column index: colId=" + idx;
if (nullMapLen > 0) { // Calculates fixlen columns chunk size regarding the 'null' flags.
int nullStartByte = cols.firstVarlengthColumn() >> 3; // Equivalent expression for: idx / 8
int startBitInByte = cols.firstVarlengthColumn() & 7; // Equivalent expression for: (expr) % 8
int nullEndByte = idx >> 3; // Equivalent expression for: idx / 8
int endBitInByte = idx & 7; // Equivalent expression for: idx % 8
int numNullsBefore = 0;
for (int i = nullStartByte; i <= nullEndByte; i++) {
byte nullmapByte = row.readByte(nullMapOffset(baseOff) + i);
if (i == nullStartByte)
// We need to clear startBitInByte least significant bits
nullmapByte &= (0xFF << startBitInByte);
if (i == nullEndByte)
// We need to clear 8-endBitInByte most significant bits
nullmapByte &= (0xFF >> (8 - endBitInByte));
numNullsBefore += Columns.numberOfNullColumns(nullmapByte);
}
idx -= numNullsBefore;
}
idx -= cols.numberOfFixsizeColumns();
// Calculate length and offset for very first (non-null) varlen column
// as vartable don't store the offset for the first varlen.
if (idx == 0) {
int off = cols.numberOfFixsizeColumns() == 0 ? dataOff : fixedSizeColumnOffset(baseOff, dataOff, cols, cols.numberOfFixsizeColumns(), nullMapLen > 0);
long len = format != null ? // Length is either diff between current offset and next varlen offset or end-of-chunk.
dataOff + format.readVarlenOffset(row, varTableOffset(baseOff, nullMapLen), 0) - off :
(baseOff + chunkLength(baseOff)) - off;
return (len << 32) | off;
}
final int varTblOff = varTableOffset(baseOff, nullMapLen);
final int vartblSize = format.readVartableSize(row, varTblOff);
assert idx > 0 && vartblSize >= idx : "Vartable index is out of bound: colId=" + idx;
// Offset of idx-th column is from base offset.
int resOff = dataOff + format.readVarlenOffset(row, varTblOff, idx - 1);
long len = (vartblSize == idx) ?
// totalLength - columnStartOffset
(baseOff + chunkLength(baseOff)) - resOff :
// nextColumnStartOffset - columnStartOffset
dataOff + format.readVarlenOffset(row, varTblOff, idx) - resOff;
return (len << 32) | resOff;
}
/**
* @param baseOff Chunk base offset.
* @return Chunk length.
*/
private int chunkLength(int baseOff) {
return readInteger(baseOff);
}
/**
* @param baseOff Chunk base offset.
* @param nullMapLen Null-map length.
* @return Vartable offset.
*/
private int varTableOffset(int baseOff, int nullMapLen) {
return baseOff + BinaryRow.CHUNK_LEN_FLD_SIZE + nullMapLen;
}
/**
* Checks the row's null-map for the given column index in the chunk.
*
* @param baseOff Chunk base offset.
* @param idx Offset of the column in the chunk.
* @return {@code true} if the column value is {@code null}, {@code false} otherwise.
*/
protected boolean isNull(int baseOff, int idx) {
int nullByte = idx >> 3; // Equivalent expression for: idx / 8
int posInByte = idx & 7; // Equivalent expression for: idx % 8
int map = row.readByte(baseOff + BinaryRow.CHUNK_LEN_FLD_SIZE + nullByte) & 0xFF;
return (map & (1 << posInByte)) != 0;
}
/**
* @return Key flags.
*/
private int keyFlags() {
short flags = readShort(FLAGS_FIELD_OFFSET);
return (flags >> RowFlags.KEY_FLAGS_OFFSET) & RowFlags.CHUNK_FLAGS_MASK;
}
/**
* @return Value flags.
*/
private int valueFlags() {
short flags = readShort(FLAGS_FIELD_OFFSET);
return (flags >> RowFlags.VAL_FLAGS_OFFSET) & RowFlags.CHUNK_FLAGS_MASK;
}
/**
* @param baseOff Chunk base offset.
* @return Null-map offset.
*/
int nullMapOffset(int baseOff) {
return baseOff + BinaryRow.CHUNK_LEN_FLD_SIZE;
}
/**
* @param colIdx Column index.
* @return Column length.
*/
private int columnLength(int colIdx) {
Column col = schema.column(colIdx);
return col.type().sizeInBytes();
}
/**
* Utility method to extract the column offset from the {@link #findColumn(int, NativeTypeSpec)} result. The
* offset is calculated from the beginning of the row.
*
* @param offLen {@code findColumn} invocation result.
* @return Column offset from the beginning of the row.
*/
private static int offset(long offLen) {
return (int)offLen;
}
/**
* Utility method to extract the column length from the {@link #findColumn(int, NativeTypeSpec)} result for
* varlen columns.
*
* @param offLen {@code findColumn} invocation result.
* @return Length of the column or {@code 0} if the column is fixed-length.
*/
private static int length(long offLen) {
return (int)(offLen >>> 32);
}
/** {@inheritDoc} */
@Override public int schemaVersion() {
return row.schemaVersion();
}
/** {@inheritDoc} */
@Override public int hash() {
return row.hash();
}
/** {@inheritDoc} */
@Override public ByteBuffer keySlice() {
return row.keySlice();
}
/** {@inheritDoc} */
@Override public ByteBuffer valueSlice() {
return row.valueSlice();
}
/** {@inheritDoc} */
@Override public void writeTo(OutputStream stream) throws IOException {
row.writeTo(stream);
}
/** {@inheritDoc} */
@Override public byte readByte(int off) {
return row.readByte(off);
}
/** {@inheritDoc} */
@Override public short readShort(int off) {
return row.readShort(off);
}
/** {@inheritDoc} */
@Override public int readInteger(int off) {
return row.readInteger(off);
}
/** {@inheritDoc} */
@Override public long readLong(int off) {
return row.readLong(off);
}
/** {@inheritDoc} */
@Override public float readFloat(int off) {
return row.readFloat(off);
}
/** {@inheritDoc} */
@Override public double readDouble(int off) {
return row.readDouble(off);
}
/** {@inheritDoc} */
@Override public String readString(int off, int len) {
return row.readString(off, len);
}
/** {@inheritDoc} */
@Override public byte[] readBytes(int off, int len) {
return row.readBytes(off, len);
}
@Override public byte[] bytes() {
return row.bytes();
}
}