Immutable table SINGLE_CELL_ARRAY_WITH_OFFSETS values starting with separator byte return null in query results
Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/edc9d12d Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/edc9d12d Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/edc9d12d Branch: refs/heads/system-catalog Commit: edc9d12dc056640526e388d7ccb1a6e2c6d3c51c Parents: 2759727 Author: Vincent Poon <vincentp...@apache.org> Authored: Wed Dec 27 15:34:32 2017 -0800 Committer: Vincent Poon <vincentp...@apache.org> Committed: Wed Dec 27 15:34:32 2017 -0800 ---------------------------------------------------------------------- .../apache/phoenix/end2end/StoreNullsIT.java | 6 +- .../phoenix/end2end/UpsertBigValuesIT.java | 64 ++++- .../phoenix/end2end/index/DropColumnIT.java | 19 +- .../phoenix/compile/ProjectionCompiler.java | 6 +- .../expression/SingleCellColumnExpression.java | 35 ++- .../apache/phoenix/index/IndexMaintainer.java | 2 +- .../NonAggregateRegionScannerFactory.java | 2 +- .../org/apache/phoenix/schema/ColumnRef.java | 4 +- .../java/org/apache/phoenix/schema/PTable.java | 21 +- .../phoenix/schema/types/PArrayDataType.java | 18 +- .../schema/types/PArrayDataTypeDecoder.java | 79 +++++- .../schema/types/PArrayDataTypeEncoder.java | 10 +- .../java/org/apache/phoenix/util/IndexUtil.java | 3 +- .../schema/ImmutableStorageSchemeTest.java | 241 +++++++++++++++++-- 14 files changed, 452 insertions(+), 58 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/it/java/org/apache/phoenix/end2end/StoreNullsIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StoreNullsIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StoreNullsIT.java index 378a9ed..d1aee9f 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/StoreNullsIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/StoreNullsIT.java @@ -145,7 +145,11 @@ public class StoreNullsIT extends ParallelStatsDisabledIT { byte[] qualifier = table.getImmutableStorageScheme()== ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS ? QueryConstants.SINGLE_KEYVALUE_COLUMN_QUALIFIER_BYTES : nameColumn.getColumnQualifierBytes(); assertTrue(rs.containsColumn(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES, qualifier)); assertTrue(rs.size() == 2); // 2 because it also includes the empty key value column - KeyValueColumnExpression colExpression = table.getImmutableStorageScheme() == ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS ? new SingleCellColumnExpression(nameColumn, "NAME", table.getEncodingScheme()) : new KeyValueColumnExpression(nameColumn); + KeyValueColumnExpression colExpression = + table.getImmutableStorageScheme() == ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS + ? new SingleCellColumnExpression(nameColumn, "NAME", + table.getEncodingScheme(), table.getImmutableStorageScheme()) + : new KeyValueColumnExpression(nameColumn); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); colExpression.evaluate(new ResultTuple(rs), ptr); assertEquals(new ImmutableBytesPtr(PVarchar.INSTANCE.toBytes("v1")), ptr); http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertBigValuesIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertBigValuesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertBigValuesIT.java index ceb76d0..9842434 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertBigValuesIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertBigValuesIT.java @@ -25,10 +25,16 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.util.Arrays; +import java.util.List; import java.util.Properties; +import org.apache.phoenix.schema.types.PDataType; +import org.apache.phoenix.schema.types.PLong; +import org.apache.phoenix.schema.types.PSmallint; import org.junit.Test; +import com.google.common.collect.Lists; public class UpsertBigValuesIT extends ParallelStatsDisabledIT { @@ -374,4 +380,60 @@ public class UpsertBigValuesIT extends ParallelStatsDisabledIT { */ conn.close(); } -} + + @Test + public void testShort() throws Exception { + List<Short> testData = + Arrays.asList(Short.MIN_VALUE, Short.MAX_VALUE, (short) (Short.MIN_VALUE + 1), + (short) (Short.MAX_VALUE - 1), (short) 0, (short) 1, (short) -1); + testValues(false, PSmallint.INSTANCE, testData); + testValues(true, PSmallint.INSTANCE, testData); + } + + @Test + public void testBigInt() throws Exception { + List<Long> testData = + Arrays.asList(Long.MIN_VALUE, Long.MAX_VALUE, Long.MIN_VALUE + 1L, + Long.MAX_VALUE - 1L, 0L, 1L, -1L); + testValues(false, PLong.INSTANCE, testData); + testValues(true, PLong.INSTANCE, testData); + } + + private <T extends Number> void testValues(boolean immutable, PDataType<?> dataType, List<T> testData) throws Exception { + String tableName = generateUniqueName(); + String ddl = + String.format("CREATE %s TABLE %s (K INTEGER PRIMARY KEY, V1 %s)", + immutable ? "IMMUTABLE" : "", tableName, dataType.getSqlTypeName()); + try (Connection conn = DriverManager.getConnection(getUrl())) { + conn.createStatement().execute(ddl); + String upsert = "UPSERT INTO " + tableName + " VALUES(?, ?)"; + PreparedStatement stmt = conn.prepareStatement(upsert); + int id = 1; + for (T testVal : testData) { + stmt.setInt(1, id++); + stmt.setObject(2, testVal, dataType.getSqlType()); + stmt.execute(); + } + conn.commit(); + String query = String.format("SELECT K,V1 FROM %s ORDER BY K ASC", tableName); + ResultSet rs = conn.createStatement().executeQuery(query); + int index = 0; + boolean failed = false; + List<String> errors = Lists.newArrayList(); + while (rs.next()) { + Number resultVal = rs.getObject(2, testData.get(0).getClass()); + T testVal = testData.get(index++); + if (!testVal.equals(resultVal)) { + errors.add(String.format("[expected=%s actual=%s] ", + testVal, resultVal)); + failed = true; + } + } + String errorMsg = + String.format("Data in table didn't match input: immutable=%s, dataType=%s, %s", + immutable, dataType.getSqlTypeName(), errors); + assertFalse(errorMsg, failed); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/DropColumnIT.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/DropColumnIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/DropColumnIT.java index badb2a6..28aa1e9 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/DropColumnIT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/DropColumnIT.java @@ -254,7 +254,9 @@ public class DropColumnIT extends ParallelStatsDisabledIT { assertNotNull(result); byte[] colValue; if (!mutable && columnEncoded) { - KeyValueColumnExpression colExpression = new SingleCellColumnExpression(dataColumn, "V2", dataTable.getEncodingScheme()); + KeyValueColumnExpression colExpression = + new SingleCellColumnExpression(dataColumn, "V2", dataTable.getEncodingScheme(), + dataTable.getImmutableStorageScheme()); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); colExpression.evaluate(new ResultTuple(result), ptr); colValue = ptr.copyBytesIfNecessary(); @@ -273,7 +275,10 @@ public class DropColumnIT extends ParallelStatsDisabledIT { result = results.next(); assertNotNull(result); if (!mutable && columnEncoded) { - KeyValueColumnExpression colExpression = new SingleCellColumnExpression(glovalIndexCol, "0:V2", globalIndexTable.getEncodingScheme()); + KeyValueColumnExpression colExpression = + new SingleCellColumnExpression(glovalIndexCol, "0:V2", + globalIndexTable.getEncodingScheme(), + globalIndexTable.getImmutableStorageScheme()); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); colExpression.evaluate(new ResultTuple(result), ptr); colValue = ptr.copyBytesIfNecessary(); @@ -293,7 +298,10 @@ public class DropColumnIT extends ParallelStatsDisabledIT { result = results.next(); assertNotNull(result); if (!mutable && columnEncoded) { - KeyValueColumnExpression colExpression = new SingleCellColumnExpression(localIndexCol, "0:V2", localIndexTable.getEncodingScheme()); + KeyValueColumnExpression colExpression = + new SingleCellColumnExpression(localIndexCol, "0:V2", + localIndexTable.getEncodingScheme(), + localIndexTable.getImmutableStorageScheme()); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); assertTrue(colExpression.evaluate(new ResultTuple(result), ptr)); colValue = ptr.copyBytesIfNecessary(); @@ -387,7 +395,10 @@ public class DropColumnIT extends ParallelStatsDisabledIT { PColumn localIndexCol = localIndex2.getColumnForColumnName(indexColumnName); byte[] colValue; if (!mutable && columnEncoded) { - KeyValueColumnExpression colExpression = new SingleCellColumnExpression(localIndexCol, indexColumnName, localIndex2.getEncodingScheme()); + KeyValueColumnExpression colExpression = + new SingleCellColumnExpression(localIndexCol, indexColumnName, + localIndex2.getEncodingScheme(), + localIndex2.getImmutableStorageScheme()); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); colExpression.evaluate(new ResultTuple(result), ptr); colValue = ptr.copyBytesIfNecessary(); http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java index a147882..f85b5a8 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java @@ -668,7 +668,11 @@ public class ProjectionCompiler { PTable table = context.getCurrentTable().getTable(); KeyValueColumnExpression keyValueColumnExpression; if (table.getImmutableStorageScheme() != ImmutableStorageScheme.ONE_CELL_PER_COLUMN) { - keyValueColumnExpression = new SingleCellColumnExpression(col, col.getName().getString(), table.getEncodingScheme()); + keyValueColumnExpression = + new SingleCellColumnExpression(col, + col.getName().getString(), + table.getEncodingScheme(), + table.getImmutableStorageScheme()); } else { keyValueColumnExpression = new KeyValueColumnExpression(col); } http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java b/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java index 8c1e0b6..e46e1fa 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/SingleCellColumnExpression.java @@ -25,7 +25,6 @@ import java.io.DataOutput; import java.io.IOException; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; -import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.WritableUtils; import org.apache.phoenix.compile.CreateTableCompiler.ViewWhereExpressionVisitor; import org.apache.phoenix.expression.visitor.ExpressionVisitor; @@ -54,12 +53,19 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { private String arrayColDisplayName; private KeyValueColumnExpression keyValueColumnExpression; private QualifierEncodingScheme encodingScheme; - + private ImmutableStorageScheme immutableStorageScheme; + public SingleCellColumnExpression() { } - - public SingleCellColumnExpression(PDatum column, byte[] cf, byte[] cq, QualifierEncodingScheme encodingScheme) { + + public SingleCellColumnExpression(ImmutableStorageScheme immutableStorageScheme) { + this.immutableStorageScheme = immutableStorageScheme; + } + + public SingleCellColumnExpression(PDatum column, byte[] cf, byte[] cq, + QualifierEncodingScheme encodingScheme, ImmutableStorageScheme immutableStorageScheme) { super(column, cf, SINGLE_KEYVALUE_COLUMN_QUALIFIER_BYTES); + this.immutableStorageScheme = immutableStorageScheme; Preconditions.checkNotNull(encodingScheme); Preconditions.checkArgument(encodingScheme != NON_ENCODED_QUALIFIERS); this.decodedColumnQualifier = encodingScheme.decode(cq); @@ -67,8 +73,9 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { setKeyValueExpression(); } - public SingleCellColumnExpression(PColumn column, String displayName, QualifierEncodingScheme encodingScheme) { + public SingleCellColumnExpression(PColumn column, String displayName, QualifierEncodingScheme encodingScheme, ImmutableStorageScheme immutableStorageScheme) { super(column, column.getFamilyName().getBytes(), SINGLE_KEYVALUE_COLUMN_QUALIFIER_BYTES); + this.immutableStorageScheme = immutableStorageScheme; Preconditions.checkNotNull(encodingScheme); Preconditions.checkArgument(encodingScheme != NON_ENCODED_QUALIFIERS); this.arrayColDisplayName = displayName; @@ -86,8 +93,6 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { } // the first position is reserved and we offset maxEncodedColumnQualifier by ENCODED_CQ_COUNTER_INITIAL_VALUE (which is the minimum encoded column qualifier) int index = decodedColumnQualifier-QueryConstants.ENCODED_CQ_COUNTER_INITIAL_VALUE+1; - byte serializedImmutableStorageScheme = ptr.get()[ptr.getOffset() + ptr.getLength() - Bytes.SIZEOF_BYTE]; - ImmutableStorageScheme immutableStorageScheme = ImmutableStorageScheme.fromSerializedValue(serializedImmutableStorageScheme); // Given a ptr to the entire array, set ptr to point to a particular element within that array ColumnValueDecoder encoderDecoder = immutableStorageScheme.getDecoder(); return encoderDecoder.decode(ptr, index); @@ -97,7 +102,18 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { public void readFields(DataInput input) throws IOException { super.readFields(input); this.decodedColumnQualifier = WritableUtils.readVInt(input); - this.encodingScheme = QualifierEncodingScheme.values()[WritableUtils.readVInt(input)]; + int serializedEncodingScheme = WritableUtils.readVInt(input); + // prior to PHOENIX-4432 we weren't writing out the immutableStorageScheme in write(), + // so we use the decodedColumnQualifier sign to determine whether it's there + if (Integer.signum(serializedEncodingScheme) == -1) { + this.immutableStorageScheme = + ImmutableStorageScheme + .fromSerializedValue((byte) WritableUtils.readVInt(input)); + serializedEncodingScheme = -serializedEncodingScheme; + } else { + this.immutableStorageScheme = ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS; + } + this.encodingScheme = QualifierEncodingScheme.values()[serializedEncodingScheme]; setKeyValueExpression(); } @@ -105,7 +121,8 @@ public class SingleCellColumnExpression extends KeyValueColumnExpression { public void write(DataOutput output) throws IOException { super.write(output); WritableUtils.writeVInt(output, decodedColumnQualifier); - WritableUtils.writeVInt(output, encodingScheme.ordinal()); + WritableUtils.writeVInt(output, -encodingScheme.ordinal()); //negative since PHOENIX-4432 + WritableUtils.writeVInt(output, immutableStorageScheme.getSerializedMetadataValue()); } public KeyValueColumnExpression getKeyValueExpression() { http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java index 500ac4b..fa60679 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java @@ -1010,7 +1010,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> { public PDataType getDataType() { return null; } - }, dataColRef.getFamily(), dataColRef.getQualifier(), encodingScheme); + }, dataColRef.getFamily(), dataColRef.getQualifier(), encodingScheme, immutableStorageScheme); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); expression.evaluate(new ValueGetterTuple(valueGetter, ts), ptr); byte[] value = ptr.copyBytesIfNecessary(); http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java index ded33cc..c097d0d 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java @@ -209,7 +209,7 @@ public class NonAggregateRegionScannerFactory extends RegionScannerFactory { int arrayKVRefSize = WritableUtils.readVInt(input); for (int i = 0; i < arrayKVRefSize; i++) { PTable.ImmutableStorageScheme scheme = EncodedColumnsUtil.getImmutableStorageScheme(scan); - KeyValueColumnExpression kvExp = scheme != PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN ? new SingleCellColumnExpression() + KeyValueColumnExpression kvExp = scheme != PTable.ImmutableStorageScheme.ONE_CELL_PER_COLUMN ? new SingleCellColumnExpression(scheme) : new KeyValueColumnExpression(); kvExp.readFields(input); arrayKVRefs.add(kvExp); http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/schema/ColumnRef.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/ColumnRef.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/ColumnRef.java index c73b860..fa3156d 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/ColumnRef.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/ColumnRef.java @@ -126,7 +126,9 @@ public class ColumnRef { } Expression expression = table.getImmutableStorageScheme() == ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS ? - new SingleCellColumnExpression(column, displayName, table.getEncodingScheme()) : new KeyValueColumnExpression(column, displayName); + new SingleCellColumnExpression(column, displayName, + table.getEncodingScheme(), table.getImmutableStorageScheme()) + : new KeyValueColumnExpression(column, displayName); if (column.getExpressionStr() != null) { String url = PhoenixRuntime.JDBC_PROTOCOL http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java index ec931b7..7e186ad 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTable.java @@ -190,14 +190,14 @@ public interface PTable extends PMetaDataEntity { } }, // stores a single cell per column family that contains all serialized column values - SINGLE_CELL_ARRAY_WITH_OFFSETS((byte)2) { + SINGLE_CELL_ARRAY_WITH_OFFSETS((byte)2, PArrayDataType.IMMUTABLE_SERIALIZATION_V2) { @Override public ColumnValueEncoder getEncoder(int numElements) { PDataType type = PVarbinary.INSTANCE; int estimatedSize = PArrayDataType.estimateSize(numElements, type); TrustedByteArrayOutputStream byteStream = new TrustedByteArrayOutputStream(estimatedSize); DataOutputStream oStream = new DataOutputStream(byteStream); - return new PArrayDataTypeEncoder(byteStream, oStream, numElements, type, SortOrder.ASC, false, PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION); + return new PArrayDataTypeEncoder(byteStream, oStream, numElements, type, SortOrder.ASC, false, getSerializationVersion()); } @Override @@ -207,15 +207,30 @@ public interface PTable extends PMetaDataEntity { }; private final byte serializedValue; - + private byte serializationVersion; + private ImmutableStorageScheme(byte serializedValue) { this.serializedValue = serializedValue; } + private ImmutableStorageScheme(byte serializedValue, byte serializationVersion) { + this.serializedValue = serializedValue; + this.serializationVersion = serializationVersion; + } + public byte getSerializedMetadataValue() { return this.serializedValue; } + public byte getSerializationVersion() { + return this.serializationVersion; + } + + @VisibleForTesting + void setSerializationVersion(byte serializationVersion) { + this.serializationVersion = serializationVersion; + } + public static ImmutableStorageScheme fromSerializedValue(byte serializedValue) { if (serializedValue < 1 || serializedValue > ImmutableStorageScheme.values().length) { return null; http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java index 7d742e2..162b235 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java @@ -75,7 +75,12 @@ public abstract class PArrayDataType<T> extends PDataType<T> { // array serialization format where bytes can be used as part of the row key public static final byte SORTABLE_SERIALIZATION_VERSION = 1; // array serialization format where bytes are immutable (does not support prepend/append or sorting) + @Deprecated public static final byte IMMUTABLE_SERIALIZATION_VERSION = 2; + // array serialization format where bytes are immutable (does not support prepend/append or sorting) + // differs from V1 in that nulls are not serialized + // we rely only on offsets to determine the presence of nulls + public static final byte IMMUTABLE_SERIALIZATION_V2 = 3; protected PArrayDataType(String sqlTypeName, int sqlType, Class clazz, PDataCodec codec, int ordinal) { super(sqlTypeName, sqlType, clazz, codec, ordinal); @@ -217,7 +222,7 @@ public abstract class PArrayDataType<T> extends PDataType<T> { } public static boolean useShortForOffsetArray(int maxoffset, byte serializationVersion) { - if (serializationVersion == IMMUTABLE_SERIALIZATION_VERSION) { + if (serializationVersion == IMMUTABLE_SERIALIZATION_VERSION || serializationVersion == IMMUTABLE_SERIALIZATION_V2) { return (maxoffset <= Short.MAX_VALUE && maxoffset >= Short.MIN_VALUE ); } // If the max offset is less than Short.MAX_VALUE then offset array can use short @@ -383,7 +388,10 @@ public abstract class PArrayDataType<T> extends PDataType<T> { int offset; if (useShort) { offset = indexOffset + (Bytes.SIZEOF_SHORT * arrayIndex); - return Bytes.toShort(bytes, offset, Bytes.SIZEOF_SHORT) + (serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION ? 0 : Short.MAX_VALUE); + return Bytes.toShort(bytes, offset, Bytes.SIZEOF_SHORT) + + (serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION + || serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_V2 ? 0 + : Short.MAX_VALUE); } else { offset = indexOffset + (Bytes.SIZEOF_INT * arrayIndex); return Bytes.toInt(bytes, offset, Bytes.SIZEOF_INT); @@ -964,7 +972,11 @@ public abstract class PArrayDataType<T> extends PDataType<T> { } } else { for (int pos : offsetPos) { - short val = serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION ? (short)pos : (short)(pos - Short.MAX_VALUE); + short val = + serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION + || serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_V2 + ? (short) pos + : (short) (pos - Short.MAX_VALUE); Bytes.putShort(offsetArr, off, val); off += Bytes.SIZEOF_SHORT; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeDecoder.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeDecoder.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeDecoder.java index 7a6ea91..22fa46c 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeDecoder.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeDecoder.java @@ -22,6 +22,7 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.phoenix.expression.Expression; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.ColumnValueDecoder; +import org.apache.phoenix.schema.SortOrder; import org.apache.phoenix.schema.tuple.Tuple; import org.apache.phoenix.util.ByteUtil; @@ -78,13 +79,45 @@ public class PArrayDataTypeDecoder implements ColumnValueDecoder { } int elementLength = 0; if (arrayIndex == (noOfElements - 1)) { - int separatorBytes = serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION ? 3 : 0; - elementLength = (bytes[currOffset + initPos] == QueryConstants.SEPARATOR_BYTE || bytes[currOffset + initPos] == QueryConstants.DESC_SEPARATOR_BYTE) ? 0 : indexOffset - - (currOffset + initPos) - separatorBytes; + // in the original IMMUTABLE_SERIALIZATION_VERSION (v1), for nulls we store + // (separatorByte, #_of_nulls) in the data. Because of the separatorByte, we can't + // distinguish between nulls and actual data values that start with the separator + // byte. We do a hack here to limit the damage by checking offsets - if the prior + // offset had a length of 0, then we know we're storing 2 or more nulls. However, we + // still can't fix the case distinguishing a single null from a short value. There + // are two kinds of separatorByte, so the results will be potentially incorrect for + // 2 short values that correspond to (separatorByte, 1) + if (serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION) { + elementLength = indexOffset - (currOffset + initPos); + if (isNullValue(arrayIndex, bytes, initPos, serializationVersion, useShort, indexOffset, currOffset, elementLength)) { + elementLength = 0; + } + } else { + int separatorBytes = serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION ? 3 : 0; + elementLength = isSeparatorByte(bytes, initPos, currOffset) && serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION ? 0 : indexOffset + - (currOffset + initPos) - separatorBytes; + } } else { - int separatorByte = serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION ? 1 : 0; - elementLength = (bytes[currOffset + initPos] == QueryConstants.SEPARATOR_BYTE || bytes[currOffset + initPos] == QueryConstants.DESC_SEPARATOR_BYTE) ? 0 : PArrayDataType.getOffset(bytes, - arrayIndex + 1, useShort, indexOffset, serializationVersion) - currOffset - separatorByte; + if (serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION) { + elementLength = PArrayDataType.getOffset(bytes, arrayIndex + 1, + useShort, indexOffset, serializationVersion) + - currOffset; + if (isNullValue(arrayIndex, bytes, initPos, serializationVersion, useShort, indexOffset, currOffset, elementLength)) { + elementLength = 0; + } + } else { + int separatorByte = + serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION + ? 1 + : 0; + elementLength = + isSeparatorByte(bytes, initPos, currOffset) + && serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION + ? 0 + : PArrayDataType.getOffset(bytes, arrayIndex + 1, + useShort, indexOffset, serializationVersion) + - currOffset - separatorByte; + } } ptr.set(bytes, currOffset + initPos, elementLength); } else { @@ -99,4 +132,38 @@ public class PArrayDataTypeDecoder implements ColumnValueDecoder { return true; } + // returns true if the prior element in the array is a null + private static boolean isNullValue(int arrayIndex, byte[] bytes, int initPos, + byte serializationVersion, boolean useShort, int indexOffset, int currOffset, + int elementLength) { + if (isSeparatorByte(bytes, initPos, currOffset)) { + if (isPriorValueZeroLength(arrayIndex, bytes, + serializationVersion, useShort, indexOffset, currOffset)) { + return true; + } else { + // if there's no prior null, there can be at most 1 null + if (elementLength == 2) { + // nullByte calculation comes from the encoding of one null + // see PArrayDataType#serializeNulls + byte nullByte = SortOrder.invert((byte)(0)); + if (bytes[initPos+currOffset+1] == nullByte) { + return true; + } + } + } + } + return false; + } + + // checks prior value length by subtracting offset of the previous item from the current offset + private static boolean isPriorValueZeroLength(int arrayIndex, byte[] bytes, byte serializationVersion, + boolean useShort, int indexOffset, int currOffset) { + return arrayIndex > 0 && currOffset - PArrayDataType.getOffset(bytes, arrayIndex - 1, + useShort, indexOffset, serializationVersion) == 0; + } + + private static boolean isSeparatorByte(byte[] bytes, int initPos, int currOffset) { + return bytes[currOffset + initPos] == QueryConstants.SEPARATOR_BYTE || bytes[currOffset + initPos] == QueryConstants.DESC_SEPARATOR_BYTE; + } + } http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeEncoder.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeEncoder.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeEncoder.java index 3dad6c3..7467981 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeEncoder.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataTypeEncoder.java @@ -92,7 +92,9 @@ public class PArrayDataTypeEncoder implements ColumnValueEncoder { // used to represent the absence of a value @Override public void appendAbsentValue() { - if (serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION && !baseType.isFixedWidth()) { + if ((serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION + || serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_V2) + && !baseType.isFixedWidth()) { offsetPos.add(-byteStream.size()); nulls++; } @@ -125,7 +127,11 @@ public class PArrayDataTypeEncoder implements ColumnValueEncoder { offsetPos.add(byteStream.size()); nulls++; } else { - nulls = PArrayDataType.serializeNulls(oStream, nulls); + // we don't serialize nulls for IMMUTABLE_SERIALIZATION_V2 + if (serializationVersion == PArrayDataType.SORTABLE_SERIALIZATION_VERSION + || serializationVersion == PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION) { + nulls = PArrayDataType.serializeNulls(oStream, nulls); + } offsetPos.add(byteStream.size()); if (sortOrder == SortOrder.DESC) { SortOrder.invert(bytes, offset, bytes, offset, len); http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java index 74f91b4..33b7383 100644 --- a/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java +++ b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java @@ -468,13 +468,14 @@ public class IndexUtil { KeyValueSchema keyValueSchema = deserializeLocalIndexJoinSchemaFromScan(scan); boolean storeColsInSingleCell = scan.getAttribute(BaseScannerRegionObserver.COLUMNS_STORED_IN_SINGLE_CELL) != null; QualifierEncodingScheme encodingScheme = EncodedColumnsUtil.getQualifierEncodingScheme(scan); + ImmutableStorageScheme immutableStorageScheme = EncodedColumnsUtil.getImmutableStorageScheme(scan); Expression[] colExpressions = storeColsInSingleCell ? new SingleCellColumnExpression[dataColumns.length] : new KeyValueColumnExpression[dataColumns.length]; for (int i = 0; i < dataColumns.length; i++) { byte[] family = dataColumns[i].getFamily(); byte[] qualifier = dataColumns[i].getQualifier(); Field field = keyValueSchema.getField(i); Expression dataColumnExpr = - storeColsInSingleCell ? new SingleCellColumnExpression(field, family, qualifier, encodingScheme) + storeColsInSingleCell ? new SingleCellColumnExpression(field, family, qualifier, encodingScheme, immutableStorageScheme) : new KeyValueColumnExpression(field, family, qualifier); colExpressions[i] = dataColumnExpr; } http://git-wip-us.apache.org/repos/asf/phoenix/blob/edc9d12d/phoenix-core/src/test/java/org/apache/phoenix/schema/ImmutableStorageSchemeTest.java ---------------------------------------------------------------------- diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/ImmutableStorageSchemeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/ImmutableStorageSchemeTest.java index d8c5cdb..3d1b176 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/ImmutableStorageSchemeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/ImmutableStorageSchemeTest.java @@ -17,9 +17,15 @@ */ package org.apache.phoenix.schema; +import static org.apache.phoenix.schema.PTable.ImmutableStorageScheme.SINGLE_CELL_ARRAY_WITH_OFFSETS; +import static org.apache.phoenix.schema.types.PArrayDataType.IMMUTABLE_SERIALIZATION_V2; +import static org.apache.phoenix.schema.types.PArrayDataType.IMMUTABLE_SERIALIZATION_VERSION; +import static org.apache.phoenix.util.ByteUtil.EMPTY_BYTE_ARRAY; import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.util.Arrays; import java.util.List; @@ -33,6 +39,11 @@ import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr; import org.apache.phoenix.query.QueryConstants; import org.apache.phoenix.schema.PTable.ImmutableStorageScheme; import org.apache.phoenix.schema.tuple.Tuple; +import org.apache.phoenix.schema.types.PDataType; +import org.apache.phoenix.schema.types.PInteger; +import org.apache.phoenix.schema.types.PSmallint; +import org.apache.phoenix.schema.types.PTinyint; +import org.apache.phoenix.schema.types.PUnsignedTinyint; import org.apache.phoenix.schema.types.PVarbinary; import org.apache.phoenix.util.ByteUtil; import org.junit.Test; @@ -55,16 +66,22 @@ public class ImmutableStorageSchemeTest { } }; private ImmutableStorageScheme immutableStorageScheme; + byte serializationVersion; - @Parameters(name="ImmutableStorageSchemeTest_immutableStorageScheme={0}}") // name is used by failsafe as file name in reports - public static ImmutableStorageScheme[] data() { - ImmutableStorageScheme[] values = ImmutableStorageScheme.values(); - // skip ONE_CELL_PER_COLUMN - return Arrays.copyOfRange(values, 1, values.length); + @Parameters(name="ImmutableStorageSchemeTest_immutableStorageScheme={0},serializationVersion={1}}") // name is used by failsafe as file name in reports + public static List<Object[]> data() { + return Arrays.asList(new Object[][] { + { SINGLE_CELL_ARRAY_WITH_OFFSETS, + IMMUTABLE_SERIALIZATION_VERSION }, + { SINGLE_CELL_ARRAY_WITH_OFFSETS, + IMMUTABLE_SERIALIZATION_V2 } + }); } - public ImmutableStorageSchemeTest(ImmutableStorageScheme immutableStorageScheme) { + public ImmutableStorageSchemeTest(ImmutableStorageScheme immutableStorageScheme, byte serializationVersion) { this.immutableStorageScheme = immutableStorageScheme; + this.immutableStorageScheme.setSerializationVersion(serializationVersion); + this.serializationVersion = serializationVersion; } @Test @@ -75,9 +92,7 @@ public class ImmutableStorageSchemeTest { children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); children.add(FALSE_EVAL_EXPRESSION); children.add(LiteralExpression.newConstant(BYTE_ARRAY2, PVarbinary.INSTANCE)); - SingleCellConstructorExpression singleCellConstructorExpression = new SingleCellConstructorExpression(immutableStorageScheme, children); - ImmutableBytesPtr ptr = new ImmutableBytesPtr(); - singleCellConstructorExpression.evaluate(null, ptr); + ImmutableBytesPtr ptr = evaluate(children); ImmutableBytesPtr ptrCopy = new ImmutableBytesPtr(ptr); ColumnValueDecoder decoder = immutableStorageScheme.getDecoder(); @@ -160,23 +175,201 @@ public class ImmutableStorageSchemeTest { children.add(nullExpression); children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); children.add(LiteralExpression.newConstant(BYTE_ARRAY2, PVarbinary.INSTANCE)); - SingleCellConstructorExpression singleCellConstructorExpression = new SingleCellConstructorExpression(immutableStorageScheme, children); + ImmutableBytesPtr ptr = evaluate(children); + + assertDecodedContents(ptr, new byte[][] {EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, BYTE_ARRAY1, BYTE_ARRAY2}); + } + + @Test + public void testTrailingNulls() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); + children.add(LiteralExpression.newConstant(BYTE_ARRAY2, PVarbinary.INSTANCE)); + children.add(nullExpression); + children.add(nullExpression); + ImmutableBytesPtr ptr = evaluate(children); + + assertDecodedContents(ptr, new byte[][] {BYTE_ARRAY1, BYTE_ARRAY2, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY}); + } + + @Test + public void testManyNulls() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + byte[][] testData = new byte[300][]; + children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); + testData[0] = BYTE_ARRAY1; + for (int i = 1; i < testData.length - 1; i++) { + children.add(nullExpression); + testData[i] = EMPTY_BYTE_ARRAY; + } + children.add(LiteralExpression.newConstant(BYTE_ARRAY2, PVarbinary.INSTANCE)); + testData[299] = BYTE_ARRAY2; + ImmutableBytesPtr ptr = evaluate(children); + + assertDecodedContents(ptr, testData); + } + + @Test + public void testSingleLeadingTrailingNull() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + children.add(nullExpression); + children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); + children.add(nullExpression); + ImmutableBytesPtr ptr = evaluate(children); + + assertDecodedContents(ptr, + new byte[][] { EMPTY_BYTE_ARRAY, BYTE_ARRAY1, EMPTY_BYTE_ARRAY }); + } + + @Test + public void testSingleMiddleNull() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + children.add(LiteralExpression.newConstant(BYTE_ARRAY1, PVarbinary.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant(BYTE_ARRAY2, PVarbinary.INSTANCE)); + ImmutableBytesPtr ptr = evaluate(children); + + assertDecodedContents(ptr, new byte[][] { BYTE_ARRAY1, EMPTY_BYTE_ARRAY, BYTE_ARRAY2 }); + } + + @Test + public void testAllShortValues() throws Exception { + int curr = Short.MIN_VALUE; + List<Expression> children = Lists.newArrayListWithExpectedSize(1); + List<Integer> failedValues = Lists.newArrayList(); + while (curr <= Short.MAX_VALUE) { + children.add(LiteralExpression.newConstant(curr, PSmallint.INSTANCE)); + ImmutableBytesPtr ptr = evaluate(children); + ColumnValueDecoder decoder = immutableStorageScheme.getDecoder(); + assertTrue(decoder.decode(ptr, 0)); + if (ptr.getLength() == 0) { + failedValues.add(curr); + } else { + if (curr != PSmallint.INSTANCE.getCodec().decodeShort(ptr.copyBytesIfNecessary(), 0, + SortOrder.ASC)) { + failedValues.add(curr); + } + } + children.remove(0); + curr++; + } + // in v1, we can't distinguish a null from two short values + if (serializationVersion == IMMUTABLE_SERIALIZATION_VERSION) { + assertTrue(failedValues.size() + " values were not properly decoded: " + failedValues, + failedValues.size() == 2); + } else { + assertTrue(failedValues.size() + " values were not properly decoded: " + failedValues, + failedValues.size() == 0); + } + } + + @Test + public void testSingleByteValues() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((byte) -128, PTinyint.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((byte) 0, PUnsignedTinyint.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((byte) 127, PUnsignedTinyint.INSTANCE)); + ImmutableBytesPtr ptr = evaluate(children); + + assertNullAtIndex(ptr, 0); + assertValueAtIndex(ptr, 1, (byte) -128, PTinyint.INSTANCE); + assertNullAtIndex(ptr, 2); + assertValueAtIndex(ptr, 3, (byte) 0, PUnsignedTinyint.INSTANCE); + assertNullAtIndex(ptr, 4); + assertValueAtIndex(ptr, 5, (byte) 127, PUnsignedTinyint.INSTANCE); + } + + @Test + public void testSeparatorByteValues() throws Exception { + List<Expression> children = Lists.newArrayListWithExpectedSize(4); + LiteralExpression nullExpression = LiteralExpression.newConstant(null); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((short) -32513, PSmallint.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((short) 32767, PSmallint.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant(Integer.MAX_VALUE, PInteger.INSTANCE)); + children.add(nullExpression); + children.add(LiteralExpression.newConstant(Integer.MIN_VALUE, PInteger.INSTANCE)); + // see if we can differentiate two nulls and {separatorByte, 2} + children.add(nullExpression); + children.add(nullExpression); + children.add(LiteralExpression.newConstant((short) -32514, PSmallint.INSTANCE)); + + ImmutableBytesPtr ptr = evaluate(children); + + assertNullAtIndex(ptr, 0); + try { + assertValueAtIndex(ptr, 1, (short) -32513, PSmallint.INSTANCE); + } catch (Exception e) { + if (serializationVersion != IMMUTABLE_SERIALIZATION_VERSION) { + fail("Failed on exception " + e); + } + } + assertNullAtIndex(ptr, 2); + try { + assertValueAtIndex(ptr, 3, (short) 32767, PSmallint.INSTANCE); + } catch (Exception e) { + if (serializationVersion != IMMUTABLE_SERIALIZATION_VERSION) { + fail("Failed on exception " + e); + } + } + assertNullAtIndex(ptr, 4); + assertValueAtIndex(ptr, 5, Integer.MAX_VALUE, PInteger.INSTANCE); + assertNullAtIndex(ptr, 6); + assertValueAtIndex(ptr, 7, Integer.MIN_VALUE, PInteger.INSTANCE); + assertNullAtIndex(ptr, 8); + assertNullAtIndex(ptr, 9); + assertValueAtIndex(ptr, 10, (short) -32514, PSmallint.INSTANCE); + } + + private void assertNullAtIndex(ImmutableBytesPtr ptr, int index) { + assertValueAtIndex(ptr, index, null, null); + } + + private void assertValueAtIndex(ImmutableBytesPtr ptr, int index, Object value, + PDataType type) { + ImmutableBytesPtr ptrCopy = new ImmutableBytesPtr(ptr); + ColumnValueDecoder decoder = immutableStorageScheme.getDecoder(); + assertTrue(decoder.decode(ptrCopy, index)); + if (value == null) { + assertArrayEquals(EMPTY_BYTE_ARRAY, ptrCopy.copyBytesIfNecessary()); + return; + } + Object decoded; + if (type.equals(PSmallint.INSTANCE)) { + decoded = type.getCodec().decodeShort(ptrCopy.copyBytesIfNecessary(), 0, SortOrder.ASC); + } else if (type.equals(PInteger.INSTANCE)) { + decoded = type.getCodec().decodeInt(ptrCopy.copyBytesIfNecessary(), 0, SortOrder.ASC); + } else { // assume byte for all other types + decoded = type.getCodec().decodeByte(ptrCopy.copyBytesIfNecessary(), 0, SortOrder.ASC); + } + assertEquals(value, decoded); + } + + private ImmutableBytesPtr evaluate(List<Expression> children) { + SingleCellConstructorExpression singleCellConstructorExpression = + new SingleCellConstructorExpression(immutableStorageScheme, children); ImmutableBytesPtr ptr = new ImmutableBytesPtr(); singleCellConstructorExpression.evaluate(null, ptr); - - ImmutableBytesPtr ptrCopy = new ImmutableBytesPtr(ptr); + return ptr; + } + + private void assertDecodedContents(ImmutableBytesPtr ptr, byte[]... contents) { ColumnValueDecoder decoder = immutableStorageScheme.getDecoder(); - assertTrue(decoder.decode(ptrCopy, 0)); - assertArrayEquals(ByteUtil.EMPTY_BYTE_ARRAY, ptrCopy.copyBytesIfNecessary()); - ptrCopy = new ImmutableBytesPtr(ptr); - assertTrue(decoder.decode(ptrCopy, 1)); - assertArrayEquals(ByteUtil.EMPTY_BYTE_ARRAY, ptrCopy.copyBytesIfNecessary()); - ptrCopy = new ImmutableBytesPtr(ptr); - assertTrue(decoder.decode(ptrCopy, 2)); - assertArrayEquals(BYTE_ARRAY1, ptrCopy.copyBytesIfNecessary()); - ptrCopy = new ImmutableBytesPtr(ptr); - assertTrue(decoder.decode(ptrCopy, 3)); - assertArrayEquals(BYTE_ARRAY2, ptrCopy.copyBytesIfNecessary()); + for (int i = 0; i < contents.length; i++) { + ImmutableBytesPtr ptrCopy = new ImmutableBytesPtr(ptr); + assertTrue(decoder.decode(ptrCopy, i)); + assertArrayEquals(contents[i], ptrCopy.copyBytesIfNecessary()); + } } - + }