| /* |
| * 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; |
| |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.time.Instant; |
| import java.time.LocalDate; |
| import java.time.LocalDateTime; |
| import java.time.LocalTime; |
| import java.util.Arrays; |
| import java.util.BitSet; |
| import java.util.Collections; |
| import java.util.Random; |
| import java.util.function.Function; |
| import java.util.stream.IntStream; |
| import org.apache.ignite.internal.schema.row.Row; |
| import org.apache.ignite.internal.schema.row.RowAssembler; |
| import org.apache.ignite.internal.util.Constants; |
| import org.apache.ignite.lang.IgniteLogger; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| import static org.apache.ignite.internal.schema.NativeTypes.BYTES; |
| import static org.apache.ignite.internal.schema.NativeTypes.DATE; |
| import static org.apache.ignite.internal.schema.NativeTypes.DOUBLE; |
| import static org.apache.ignite.internal.schema.NativeTypes.FLOAT; |
| import static org.apache.ignite.internal.schema.NativeTypes.INT16; |
| import static org.apache.ignite.internal.schema.NativeTypes.INT32; |
| import static org.apache.ignite.internal.schema.NativeTypes.INT64; |
| import static org.apache.ignite.internal.schema.NativeTypes.INT8; |
| import static org.apache.ignite.internal.schema.NativeTypes.STRING; |
| import static org.apache.ignite.internal.schema.NativeTypes.UUID; |
| import static org.apache.ignite.internal.schema.NativeTypes.datetime; |
| import static org.apache.ignite.internal.schema.NativeTypes.time; |
| import static org.apache.ignite.internal.schema.NativeTypes.timestamp; |
| import static org.apache.ignite.internal.schema.TestUtils.randomBytes; |
| import static org.apache.ignite.internal.schema.TestUtils.randomString; |
| import static org.junit.jupiter.api.Assertions.assertArrayEquals; |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| |
| /** |
| * Tests row assembling and reading. |
| */ |
| public class RowTest { |
| /** Random. */ |
| private Random rnd; |
| |
| /** |
| * Initialization. |
| */ |
| @BeforeEach |
| public void initRandom() { |
| long seed = System.currentTimeMillis(); |
| |
| IgniteLogger.forClass(RowTest.class).info("Using seed: " + seed + "L; //"); |
| |
| rnd = new Random(seed); |
| } |
| |
| /** |
| * Check row serialization for schema with nullable fix-sized columns only. |
| */ |
| @Test |
| public void nullableFixSizedColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyByteCol", INT8, false), |
| new Column("keyShortCol", INT16, false), |
| new Column("keyIntCol", INT32, false), |
| new Column("keyLongCol", INT64, false), |
| new Column("keyFloatCol", FLOAT, false), |
| new Column("keyDoubleCol", DOUBLE, false), |
| new Column("keyUuidCol", UUID, false), |
| new Column("keyDateCol", DATE, false), |
| new Column("keyTimeCol", time(), false), |
| new Column("keyDateTimeCol", datetime(), false), |
| new Column("keyTimeStampCol", timestamp(), false), |
| new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), false), |
| new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), false) |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valByteCol", INT8, false), |
| new Column("valShortCol", INT16, false), |
| new Column("valIntCol", INT32, false), |
| new Column("valLongCol", INT64, false), |
| new Column("valFloatCol", FLOAT, false), |
| new Column("valDoubleCol", DOUBLE, false), |
| new Column("valUuidCol", UUID, false), |
| new Column("valDateCol", DATE, false), |
| new Column("valTimeCol", time(), false), |
| new Column("valDateTimeCol", datetime(), false), |
| new Column("valTimeStampCol", timestamp(), false), |
| new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), false), |
| new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), false) |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with non-nullable fix-sized columns only. |
| */ |
| @Test |
| public void fixSizedColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyByteCol", INT8, true), |
| new Column("keyShortCol", INT16, true), |
| new Column("keyIntCol", INT32, true), |
| new Column("keyLongCol", INT64, true), |
| new Column("keyFloatCol", FLOAT, true), |
| new Column("keyDoubleCol", DOUBLE, true), |
| new Column("keyUuidCol", UUID, true), |
| new Column("keyDateCol", DATE, true), |
| new Column("keyTimeCol", time(), true), |
| new Column("keyDateTimeCol", datetime(), true), |
| new Column("keyTimeStampCol", timestamp(), true), |
| new Column("keyBitmask1Col", NativeTypes.bitmaskOf(4), true), |
| new Column("keyBitmask2Col", NativeTypes.bitmaskOf(22), true), |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valByteCol", INT8, true), |
| new Column("valShortCol", INT16, true), |
| new Column("valIntCol", INT32, true), |
| new Column("valLongCol", INT64, true), |
| new Column("valFloatCol", FLOAT, true), |
| new Column("valDoubleCol", DOUBLE, true), |
| new Column("valUuidCol", UUID, true), |
| new Column("valDateCol", DATE, true), |
| new Column("valTimeCol", time(), true), |
| new Column("valDateTimeCol", datetime(), true), |
| new Column("valTimeStampCol", timestamp(), true), |
| new Column("valBitmask1Col", NativeTypes.bitmaskOf(4), true), |
| new Column("valBitmask2Col", NativeTypes.bitmaskOf(22), true), |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with various columns. |
| */ |
| @Test |
| public void mixedColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyByteCol", INT8, false), |
| new Column("keyShortCol", INT16, false), |
| new Column("keyIntCol", INT32, false), |
| new Column("keyLongCol", INT64, false), |
| new Column("keyDateTimeCol", datetime(), false), |
| new Column("keyBytesCol", BYTES, false), |
| new Column("keyStringCol", STRING, false), |
| new Column("keyNumberCol", NativeTypes.numberOf(9), false), |
| new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false), |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valByteCol", INT8, true), |
| new Column("valShortCol", INT16, true), |
| new Column("valIntCol", INT32, true), |
| new Column("valLongCol", INT64, true), |
| new Column("valDateTimeCol", datetime(), true), |
| new Column("valBytesCol", BYTES, true), |
| new Column("valStringCol", STRING, true), |
| new Column("valNumberCol", NativeTypes.numberOf(9), true), |
| new Column("valDecimalCol", NativeTypes.decimalOf(20, 3), true), |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with various columns. |
| */ |
| @Test |
| public void temporalColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyTimestampCol1", timestamp(0), false), |
| new Column("keyTimestampCol2", timestamp(1), false), |
| new Column("keyTimestampCol3", timestamp(4), false), |
| new Column("keyTimestampCol4", timestamp(9), false), |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valDateTimeCol1", datetime(0), false), |
| new Column("valDateTimeCol2", datetime(1), true), |
| new Column("valDateTimeCol3", datetime(4), false), |
| new Column("valDateTimeCol4", datetime(9), true), |
| new Column("valTimeCol1", time(0), true), |
| new Column("valTimeCol2", time(1), true), |
| new Column("valTimeCol3", time(4), false), |
| new Column("valTimeCol4", time(9), false), |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with non-nullable varlen columns only. |
| */ |
| @Test |
| public void varlenColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyBytesCol", BYTES, false), |
| new Column("keyStringCol", STRING, false), |
| new Column("keyNumberCol", NativeTypes.numberOf(9), false), |
| new Column("keyDecimalCol", NativeTypes.decimalOf(20, 3), false), |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valBytesCol", BYTES, false), |
| new Column("valStringCol", STRING, false), |
| new Column("valNumberCol", NativeTypes.numberOf(9), false), |
| new Column("valDecimalCol", NativeTypes.decimalOf(20, 3), false), |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with nullable varlen columns only. |
| */ |
| @Test |
| public void nullableVarlenColumns() { |
| Column[] keyCols = new Column[]{ |
| new Column("keyBytesCol", BYTES, true), |
| new Column("keyStringCol", STRING, true), |
| }; |
| |
| Column[] valCols = new Column[]{ |
| new Column("valBytesCol", BYTES, true), |
| new Column("valStringCol", STRING, true), |
| new Column("valNumberCol", NativeTypes.numberOf(9), true), |
| new Column("valDecimalCol", NativeTypes.decimalOf(20, 3), true), |
| }; |
| |
| checkSchema(keyCols, valCols); |
| } |
| |
| /** |
| * Check row serialization for schema with large varlen columns (64Kb+). |
| */ |
| @Test |
| public void largeVarlenColumns() { |
| Column[] keyCols = new Column[] { |
| new Column("keyBytesCol", BYTES, false), |
| new Column("keyStringCol", STRING, false), |
| }; |
| |
| Column[] valCols = new Column[] { |
| new Column("valBytesCol", BYTES, true), |
| new Column("valStringCol", STRING, true), |
| }; |
| |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch, t -> (t.spec() == NativeTypeSpec.BYTES) ? |
| randomBytes(rnd, rnd.nextInt(Constants.MiB) + (2 << 16)) : |
| randomString(rnd, rnd.nextInt(Constants.MiB) + (2 << 16))); |
| |
| checkValues(sch, checkArr); |
| |
| for (int idx = 0; idx < checkArr.length; idx++) { |
| if (!sch.column(idx).nullable()) |
| continue; |
| |
| Object prev = checkArr[idx]; |
| checkArr[idx] = null; |
| |
| checkValues(sch, checkArr); |
| |
| checkArr[idx] = prev; |
| } |
| } |
| |
| /** |
| * Check row serialization for 256+ fixsized columns. |
| */ |
| @Test |
| public void mediumLenWithFixSizedColumns() { |
| Column[] keyCols = IntStream.range(0, 300) |
| .mapToObj(i -> new Column("keyCol" + i, INT8, false)) |
| .toArray(Column[]::new); |
| Column[] valCols = IntStream.range(0, 330) |
| .mapToObj(i -> new Column("valCol" + i, INT8, true)) |
| .toArray(Column[]::new); |
| |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch); |
| |
| checkValues(sch, checkArr); |
| } |
| |
| /** |
| * Check row serialization for 256+ varlen columns. |
| */ |
| @Test |
| public void mediumLenWithVarlenColumns() { |
| Column[] keyCols = IntStream.range(0, 300) |
| .mapToObj(i -> new Column("keyCol" + i, STRING, false)) |
| .toArray(Column[]::new); |
| Column[] valCols = IntStream.range(0, 300) |
| .mapToObj(i -> new Column("valCol" + i, STRING, true)) |
| .toArray(Column[]::new); |
| |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch, t -> randomString(rnd, rnd.nextInt(5))); |
| |
| checkValues(sch, checkArr); |
| } |
| |
| /** |
| * Check row serialization for 64K+ fixlen columns. |
| */ |
| @Test |
| public void largeLenWithFixSizedColumns() { |
| Column[] keyCols = IntStream.range(0, (2 << 16) + rnd.nextInt(20)) |
| .mapToObj(i -> new Column("keyCol" + i, INT8, false)) |
| .toArray(Column[]::new); |
| Column[] valCols = IntStream.range(0, (2 << 16) + rnd.nextInt(20)) |
| .mapToObj(i -> new Column("valCol" + i, INT8, true)) |
| .toArray(Column[]::new); |
| |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch); |
| |
| checkValues(sch, checkArr); |
| } |
| |
| /** |
| * Check row serialization for 1K+ varlen columns with total chunk length of 64k+. |
| */ |
| @Test |
| public void largeLenWithVarlenColumns() { |
| Column[] keyCols = IntStream.range(0, 1000 + rnd.nextInt(20)) |
| .mapToObj(i -> new Column("keyCol" + i, STRING, false)) |
| .toArray(Column[]::new); |
| |
| Column[] valCols = IntStream.range(0, 1000 + rnd.nextInt(20)) |
| .mapToObj(i -> new Column("valCol" + i, STRING, true)) |
| .toArray(Column[]::new); |
| |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch, t -> randomString(rnd, 65 + rnd.nextInt(500))); |
| |
| checkValues(sch, checkArr); |
| } |
| |
| /** |
| * Checks schema is independent from prodived column order. |
| * |
| * @param keyCols Key columns. |
| * @param valCols Value columns. |
| */ |
| private void checkSchema(Column[] keyCols, Column[] valCols) { |
| checkSchemaShuffled(keyCols, valCols); |
| |
| shuffle(keyCols); |
| shuffle(valCols); |
| |
| checkSchemaShuffled(keyCols, valCols); |
| } |
| |
| /** |
| * Checks schema for given columns. |
| * |
| * @param keyCols Key columns. |
| * @param valCols Value columns. |
| */ |
| private void checkSchemaShuffled(Column[] keyCols, Column[] valCols) { |
| SchemaDescriptor sch = new SchemaDescriptor(java.util.UUID.randomUUID(), 1, keyCols, valCols); |
| |
| Object[] checkArr = generateRowValues(sch); |
| |
| checkValues(sch, checkArr); |
| |
| for (int idx = 0; idx < checkArr.length; idx++) { |
| if (!sch.column(idx).nullable()) |
| continue; |
| |
| Object prev = checkArr[idx]; |
| checkArr[idx] = null; |
| |
| checkValues(sch, checkArr); |
| |
| checkArr[idx] = prev; |
| } |
| } |
| |
| /** |
| * Generate row values for given row schema. |
| * |
| * @param schema Row schema. |
| * @return Row values. |
| */ |
| private Object[] generateRowValues(SchemaDescriptor schema) { |
| return generateRowValues(schema, this::generateRandomValue); |
| } |
| |
| /** |
| * Generate row values for given row schema. |
| * |
| * @param schema Row schema. |
| * @param rnd Function that returns random value for the type. |
| * @return Row values. |
| */ |
| private Object[] generateRowValues(SchemaDescriptor schema, Function<NativeType, Object> rnd) { |
| Object[] res = new Object[schema.length()]; |
| |
| for (int i = 0; i < res.length; i++) { |
| NativeType type = schema.column(i).type(); |
| |
| res[i] = rnd.apply(type); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Generates random value of a given type. |
| * |
| * @param type Value type. |
| * @return Random value of requested type. |
| */ |
| private Object generateRandomValue(NativeType type) { |
| return TestUtils.generateRandomValue(rnd, type); |
| } |
| |
| /** |
| * Validates row values after serialization-then-deserialization. |
| * |
| * @param schema Row schema. |
| * @param vals Row values. |
| */ |
| private void checkValues(SchemaDescriptor schema, Object... vals) { |
| assertEquals(schema.keyColumns().length() + schema.valueColumns().length(), vals.length); |
| |
| int nonNullVarLenKeyCols = 0; |
| int nonNullVarLenValCols = 0; |
| int nonNullVarLenKeySize = 0; |
| int nonNullVarLenValSize = 0; |
| |
| for (int i = 0; i < vals.length; i++) { |
| NativeTypeSpec type = schema.column(i).type().spec(); |
| |
| if (vals[i] != null && !type.fixedLength()) { |
| if (type == NativeTypeSpec.BYTES) { |
| byte[] val = (byte[])vals[i]; |
| if (schema.isKeyColumn(i)) { |
| nonNullVarLenKeyCols++; |
| nonNullVarLenKeySize += val.length; |
| } |
| else { |
| nonNullVarLenValCols++; |
| nonNullVarLenValSize += val.length; |
| } |
| } |
| else if (type == NativeTypeSpec.STRING) { |
| if (schema.isKeyColumn(i)) { |
| nonNullVarLenKeyCols++; |
| nonNullVarLenKeySize += RowAssembler.utf8EncodedLength((CharSequence)vals[i]); |
| } |
| else { |
| nonNullVarLenValCols++; |
| nonNullVarLenValSize += RowAssembler.utf8EncodedLength((CharSequence)vals[i]); |
| } |
| } |
| else if (type == NativeTypeSpec.NUMBER) { |
| if (schema.isKeyColumn(i)) { |
| nonNullVarLenKeyCols++; |
| nonNullVarLenKeySize += RowAssembler.sizeInBytes((BigInteger)vals[i]); |
| } |
| else { |
| nonNullVarLenValCols++; |
| nonNullVarLenValSize += RowAssembler.sizeInBytes((BigInteger)vals[i]); |
| } |
| } |
| else if (type == NativeTypeSpec.DECIMAL) { |
| if (schema.isKeyColumn(i)) { |
| nonNullVarLenKeyCols++; |
| nonNullVarLenKeySize += RowAssembler.sizeInBytes((BigDecimal)vals[i]); |
| } |
| else { |
| nonNullVarLenValCols++; |
| nonNullVarLenValSize += RowAssembler.sizeInBytes((BigDecimal)vals[i]); |
| } |
| } |
| else |
| throw new IllegalStateException("Unsupported variable-length type: " + type); |
| } |
| } |
| |
| RowAssembler asm = new RowAssembler( |
| schema, |
| nonNullVarLenKeySize, |
| nonNullVarLenKeyCols, |
| nonNullVarLenValSize, |
| nonNullVarLenValCols); |
| |
| for (int i = 0; i < vals.length; i++) { |
| if (vals[i] == null) |
| asm.appendNull(); |
| else { |
| NativeType type = schema.column(i).type(); |
| |
| switch (type.spec()) { |
| case INT8: |
| asm.appendByte((Byte)vals[i]); |
| break; |
| |
| case INT16: |
| asm.appendShort((Short)vals[i]); |
| break; |
| |
| case INT32: |
| asm.appendInt((Integer)vals[i]); |
| break; |
| |
| case INT64: |
| asm.appendLong((Long)vals[i]); |
| break; |
| |
| case FLOAT: |
| asm.appendFloat((Float)vals[i]); |
| break; |
| |
| case DOUBLE: |
| asm.appendDouble((Double)vals[i]); |
| break; |
| |
| case UUID: |
| asm.appendUuid((java.util.UUID)vals[i]); |
| break; |
| |
| case STRING: |
| asm.appendString((String)vals[i]); |
| break; |
| |
| case NUMBER: |
| asm.appendNumber((BigInteger)vals[i]); |
| break; |
| |
| case DECIMAL: |
| asm.appendDecimal((BigDecimal)vals[i]); |
| break; |
| |
| case BYTES: |
| asm.appendBytes((byte[])vals[i]); |
| break; |
| |
| case BITMASK: |
| asm.appendBitmask((BitSet)vals[i]); |
| break; |
| |
| case DATE: |
| asm.appendDate((LocalDate)vals[i]); |
| break; |
| |
| case TIME: |
| asm.appendTime((LocalTime)vals[i]); |
| break; |
| |
| case DATETIME: |
| asm.appendDateTime((LocalDateTime)vals[i]); |
| break; |
| |
| case TIMESTAMP: |
| asm.appendTimestamp((Instant)vals[i]); |
| break; |
| |
| default: |
| throw new IllegalStateException("Unsupported test type: " + type); |
| } |
| } |
| } |
| |
| byte[] data = asm.toBytes(); |
| |
| Row row = new Row(schema, new ByteBufferRow(data)); |
| |
| for (int i = 0; i < vals.length; i++) { |
| Column col = schema.column(i); |
| |
| NativeTypeSpec type = col.type().spec(); |
| |
| if (type == NativeTypeSpec.BYTES) |
| assertArrayEquals((byte[])vals[i], (byte[])NativeTypeSpec.BYTES.objectValue(row, i), |
| "Failed for column: " + col); |
| else |
| assertEquals(vals[i], type.objectValue(row, i), "Failed for column: " + col); |
| } |
| } |
| |
| /** |
| * Shuffle columns. |
| */ |
| private void shuffle(Column[] cols) { |
| Collections.shuffle(Arrays.asList(cols)); |
| } |
| } |