| /* |
| * 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.drill.exec.vector; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import org.apache.drill.common.types.TypeProtos; |
| import org.apache.drill.common.types.TypeProtos.MajorType; |
| import org.apache.drill.common.types.Types; |
| import org.apache.drill.exec.memory.RootAllocator; |
| import org.apache.drill.exec.record.MaterializedField; |
| import org.apache.drill.exec.record.TransferPair; |
| import org.apache.drill.exec.vector.complex.EmptyValuePopulator; |
| import org.apache.drill.exec.vector.complex.MapVector; |
| import org.apache.drill.exec.vector.complex.RepeatedMapVector; |
| import org.apache.drill.exec.vector.complex.impl.NullableVarCharWriterImpl; |
| import org.apache.drill.exec.vector.complex.impl.SingleMapWriter; |
| import org.apache.drill.exec.vector.complex.writer.BaseWriter.ListWriter; |
| import org.apache.drill.exec.vector.complex.writer.BaseWriter.MapWriter; |
| import org.apache.drill.exec.vector.complex.writer.FieldWriter; |
| import org.apache.drill.exec.vector.complex.writer.IntWriter; |
| import org.junit.AfterClass; |
| import org.junit.BeforeClass; |
| import org.junit.Test; |
| |
| import io.netty.buffer.DrillBuf; |
| |
| public class VectorTest { |
| |
| private static RootAllocator allocator; |
| |
| @BeforeClass |
| public static void setup() { |
| allocator = new RootAllocator(10_000_000); |
| } |
| |
| @AfterClass |
| public static void tearDown() { |
| allocator.close(); |
| } |
| |
| @Test |
| public void testVarChar() { |
| MaterializedField field = MaterializedField.create("stringCol", Types.required(TypeProtos.MinorType.VARCHAR)); |
| try (VarCharVector v = new VarCharVector(field, allocator)) { |
| v.allocateNew(1000, 128); |
| VarCharVector.Accessor va = v.getAccessor(); |
| UInt4Vector ov = v.getOffsetVector(); |
| UInt4Vector.Accessor ova = ov.getAccessor(); |
| |
| assertEquals(1024, v.getBuffer().capacity()); |
| // Note: offset vector size is (128 + 1) rounded up |
| assertEquals(256 * 4, ov.getBuffer().capacity()); |
| |
| // Zero fill so the following is deterministic |
| |
| v.getBuffer().setZero(0, 1024); |
| ov.getBuffer().setZero(0, 256 * 4); |
| |
| // O-size is special |
| |
| v.getMutator().setValueCount(0); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, ova.getValueCount()); |
| |
| // Fill-empties at 0 is also special. |
| |
| v.getMutator().fillEmpties(-1, 0); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, ova.getValueCount()); |
| |
| // Write one value |
| |
| v.getMutator().setSafe(0, "foo".getBytes()); |
| v.getMutator().setValueCount(1); |
| assertEquals(1, va.getValueCount()); |
| assertEquals(2, ova.getValueCount()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(3, ova.get(1)); |
| |
| // Pretend to skip a value |
| |
| v.getMutator().fillEmpties(0, 2); |
| // fillEmpties does not change the value count |
| assertEquals(0, ova.get(0)); |
| assertEquals(3, ova.get(1)); // First value |
| assertEquals(3, ova.get(2)); // Filled |
| assertEquals(0, ova.get(3)); // Should not be set |
| |
| // Write one more value |
| |
| v.getMutator().setSafe(2, "mumble".getBytes()); |
| v.getMutator().setValueCount(2); |
| assertEquals(2, va.getValueCount()); |
| assertEquals(3, ova.getValueCount()); |
| assertEquals(3, ova.get(1)); |
| assertEquals(3, ova.get(2)); |
| assertEquals(9, ova.get(3)); |
| } |
| } |
| |
| /** |
| * Verify the delicate, fragile logic for setting the value count and |
| * filling empty values. Some operators and readers first write values, |
| * then set the value count at the end of the batch. The "complex writers" |
| * set the value count after each value. The "lastSet" count tracks the |
| * last value actually written, but is set in fillEmpties(), which is |
| * also called when setting the value count. |
| */ |
| |
| @Test |
| public void testNullableVarChar() { |
| MaterializedField field = MaterializedField.create("stringCol", Types.optional(TypeProtos.MinorType.VARCHAR)); |
| try (NullableVarCharVector v = new NullableVarCharVector(field, allocator)) { |
| int targetDataLength = 1000; |
| int targetValueCount = 128; |
| v.allocateNew(targetDataLength, targetValueCount); |
| NullableVarCharVector.Accessor va = v.getAccessor(); |
| NullableVarCharVector.Mutator vm = v.getMutator(); |
| VarCharVector dv = v.getValuesVector(); |
| VarCharVector.Accessor da = dv.getAccessor(); |
| UInt4Vector ov = dv.getOffsetVector(); |
| UInt4Vector.Accessor ova = ov.getAccessor(); |
| UInt1Vector bv = v.getBitsVector(); |
| |
| // Right at edge, so target offset vector is larger |
| int actualDataLength = 1024; |
| int targetOffsetCount = 2 * targetValueCount; |
| assertEquals(actualDataLength, dv.getBuffer().capacity()); |
| assertEquals(targetOffsetCount * 4, ov.getBuffer().capacity()); |
| assertEquals(targetValueCount, bv.getBuffer().capacity()); |
| |
| // Zero fill so the following is deterministic |
| // Bits are already zero |
| |
| v.getBuffer().setZero(0, 1024); |
| ov.getBuffer().setZero(0, 256 * 4); |
| |
| // Initial setup. Valid only because of zero-fill and |
| // how intial values happen to be set up. |
| |
| assertEquals(0, va.getValueCount()); |
| assertEquals(-1, vm.getLastSet()); |
| assertEquals(0, bv.getAccessor().getValueCount()); |
| assertEquals(0, da.getValueCount()); |
| assertEquals(0, ova.getValueCount()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(0, ova.get(1)); |
| |
| // O-size is special |
| |
| v.getMutator().setValueCount(0); |
| assertEquals(-1, vm.getLastSet()); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, bv.getAccessor().getValueCount()); |
| assertEquals(0, da.getValueCount()); |
| assertEquals(0, ova.getValueCount()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(0, ova.get(1)); |
| |
| // Fill-empties at 0 is also special. |
| |
| v.getMutator().fillEmpties(0); |
| assertEquals(0, vm.getLastSet()); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, ova.getValueCount()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(0, ova.get(1)); |
| |
| // Write one value |
| |
| byte[] bytes = "foo".getBytes(); |
| vm.setSafe(0, bytes, 0, bytes.length); |
| assertEquals(0, vm.getLastSet()); |
| vm.setValueCount(1); |
| assertEquals(0, vm.getLastSet()); |
| assertEquals(1, va.getValueCount()); |
| assertEquals(2, ova.getValueCount()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(bytes.length, ova.get(1)); |
| |
| // Pretend to skip a value |
| |
| v.getMutator().fillEmpties(2); |
| // Optimistically pre-set for value we're about to write |
| assertEquals(2, vm.getLastSet()); |
| // fillEmpties does not change the value count |
| assertEquals(0, ova.get(0)); |
| assertEquals(3, ova.get(1)); // First value |
| assertEquals(3, ova.get(2)); // Filled |
| assertEquals(0, ova.get(3)); // Should not be set |
| |
| // Calling fillEmpties() twice is idempotent |
| |
| v.getMutator().fillEmpties(2); |
| assertEquals(2, v.getMutator().getLastSet()); |
| assertEquals(0, ova.get(0)); |
| assertEquals(3, ova.get(1)); // First value |
| assertEquals(3, ova.get(2)); // Filled |
| assertEquals(0, ova.get(3)); // Should not be set |
| |
| // Write one more value |
| |
| byte[] second = "mumble".getBytes(); |
| vm.setSafe(2, second, 0, second.length); |
| assertEquals(2, v.getMutator().getLastSet()); |
| vm.setValueCount(3); |
| assertEquals(2, v.getMutator().getLastSet()); |
| assertEquals(3, va.getValueCount()); |
| assertEquals(4, ova.getValueCount()); |
| assertEquals(3, ova.get(1)); |
| assertEquals(3, ova.get(2)); |
| assertEquals(9, ova.get(3)); |
| |
| // Skip two values |
| |
| v.getMutator().setValueCount(5); |
| assertEquals(4, v.getMutator().getLastSet()); |
| assertEquals(5, va.getValueCount()); |
| assertEquals(6, ova.getValueCount()); |
| assertEquals(9, ova.get(3)); |
| assertEquals(9, ova.get(4)); |
| assertEquals(9, ova.get(5)); |
| |
| // Skip a large number of values. Finish the vector |
| // right where the offset vector would have to increase |
| // in length |
| |
| v.getMutator().setValueCount(targetOffsetCount); |
| assertEquals(targetOffsetCount - 1, v.getMutator().getLastSet()); |
| assertEquals(targetOffsetCount, va.getValueCount()); |
| assertEquals(targetOffsetCount + 1, ova.getValueCount()); |
| assertEquals(9, ova.get(targetOffsetCount-1)); |
| assertEquals(9, ova.get(targetOffsetCount)); |
| assertEquals(0, ova.get(targetOffsetCount + 1)); |
| } |
| } |
| |
| @Test |
| public void testNullableVarCharWriter() throws Exception { |
| MaterializedField field = MaterializedField.create("stringCol", Types.optional(TypeProtos.MinorType.VARCHAR)); |
| try (NullableVarCharVector v = new NullableVarCharVector(field, allocator); |
| DrillBuf buf = allocator.buffer(100)) { |
| v.allocateNew(1000, 128); |
| @SuppressWarnings("resource") |
| FieldWriter w = new NullableVarCharWriterImpl(v, null); |
| |
| // Write in locations 1 and 3. |
| |
| w.setPosition(0); |
| buf.setBytes(0, "foo".getBytes()); |
| w.writeVarChar(0, 3, buf); |
| |
| w.setPosition(2); |
| buf.setBytes(0, "mumble".getBytes()); |
| w.writeVarChar(0, 6, buf); |
| |
| // Don't close the writer; it clears the vector |
| // w.close(); |
| |
| VarCharVector dv = v.getValuesVector(); |
| UInt4Vector ov = dv.getOffsetVector(); |
| UInt4Vector.Accessor ova = ov.getAccessor(); |
| |
| v.getMutator().setValueCount(2); |
| assertEquals(2, v.getAccessor().getValueCount()); |
| assertEquals(3, ova.getValueCount()); |
| assertEquals(3, ova.get(1)); |
| assertEquals(3, ova.get(2)); |
| assertEquals(9, ova.get(3)); |
| } |
| } |
| |
| @Test |
| public void testEmptyValuePopulator() throws Exception { |
| MaterializedField field = MaterializedField.create("offsets", Types.required(TypeProtos.MinorType.UINT4)); |
| try (UInt4Vector v = new UInt4Vector(field, allocator)) { |
| EmptyValuePopulator pop = new EmptyValuePopulator(v); |
| UInt4Vector.Accessor va = v.getAccessor(); |
| UInt4Vector.Mutator vm = v.getMutator(); |
| |
| v.allocateNew(128); |
| |
| // Zero case; for zero-length batches |
| |
| vm.setValueCount(0); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, va.get(0)); |
| |
| // Start a record 0 value. |
| |
| pop.populate(0); |
| assertEquals(1, va.getValueCount()); |
| assertEquals(0, va.get(0)); |
| |
| // Pretend batch count is 1. Offset vector is |
| // currently in special 0-size state. |
| |
| pop.populate(1); |
| vm.setValueCount(1); |
| assertEquals(1, va.getValueCount()); |
| assertEquals(0, va.get(0)); |
| assertEquals(0, va.get(1)); |
| |
| // Pretend values are [xx] and [xxx] |
| |
| vm.set(1, 2); |
| vm.set(2, 5); |
| vm.set(3, 0); |
| vm.setValueCount(3); |
| assertEquals(3, va.getValueCount()); |
| assertEquals(5, va.get(2)); |
| assertEquals(0, va.get(3)); |
| |
| // Pretend that the record count is 2 for the two |
| // values above. |
| |
| pop.populate(2); |
| assertEquals(3, va.getValueCount()); |
| assertEquals(5, va.get(2)); |
| assertEquals(0, va.get(3)); |
| |
| // Pretend, instead we skipped records 2, 3 and 4 |
| |
| pop.populate(5); |
| assertEquals(6, va.getValueCount()); |
| assertEquals(5, va.get(2)); |
| assertEquals(5, va.get(3)); |
| assertEquals(5, va.get(4)); |
| assertEquals(5, va.get(5)); |
| assertEquals(0, va.get(6)); |
| } |
| } |
| |
| @Test |
| public void testRepeatedMapCount() throws Exception { |
| try (RepeatedMapVector v = buildRepeatedMap()) { |
| |
| IntVector iv = getInner(v); |
| RepeatedMapVector.Accessor va = v.getAccessor(); |
| RepeatedMapVector.Mutator vm = v.getMutator(); |
| IntVector.Accessor ia = iv.getAccessor(); |
| IntVector.Mutator im = iv.getMutator(); |
| UInt4Vector ov = v.getOffsetVector(); |
| UInt4Vector.Accessor oa = ov.getAccessor(); |
| UInt4Vector.Mutator om = ov.getMutator(); |
| |
| // Zero fill so the following is deterministic |
| |
| ov.getBuffer().setZero(0, 6 * 4); |
| |
| // Initial state |
| |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, ia.getValueCount()); |
| assertEquals(0, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| |
| // Record size = 0 |
| |
| vm.setValueCount(0); |
| assertEquals(0, va.getValueCount()); |
| assertEquals(0, ia.getValueCount()); |
| assertEquals(0, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| |
| // Record size = 1, so, implicit record 1 (1-based) of [] |
| |
| vm.setValueCount(1); |
| assertEquals(1, va.getValueCount()); |
| assertEquals(0, ia.getValueCount()); |
| assertEquals(2, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(0, oa.get(1)); |
| |
| // Record 2 (1-based) is [10, 20] |
| |
| im.set(0, 10); |
| im.set(1, 20); |
| im.setValueCount(2); |
| om.set(2, 2); |
| om.setValueCount(3); |
| vm.setValueCount(2); |
| assertEquals(2, va.getValueCount()); |
| assertEquals(2, ia.getValueCount()); |
| assertEquals(3, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(0, oa.get(1)); |
| assertEquals(2, oa.get(2)); |
| assertEquals(0, oa.get(3)); |
| |
| // Batch size = 4, so implicit record 2, 4 of [] |
| |
| vm.setValueCount(4); |
| assertEquals(4, va.getValueCount()); |
| assertEquals(2, ia.getValueCount()); |
| assertEquals(5, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(0, oa.get(1)); |
| assertEquals(2, oa.get(2)); |
| assertEquals(2, oa.get(3)); |
| assertEquals(2, oa.get(4)); |
| assertEquals(0, oa.get(5)); |
| } |
| } |
| |
| @Test |
| public void testRepeatedCopySafe() throws Exception { |
| try (RepeatedMapVector v = buildRepeatedMap(); |
| RepeatedMapVector f = buildFromMap()) { |
| |
| RepeatedMapVector.Mutator vm = v.getMutator(); |
| UInt4Vector ov = v.getOffsetVector(); |
| UInt4Vector.Accessor oa = ov.getAccessor(); |
| UInt4Vector.Mutator om = ov.getMutator(); |
| |
| TransferPair tp = f.makeTransferPair(v); |
| |
| tp.copyValueSafe(0, 0); |
| |
| // CopyValue does not change the value count |
| //assertEquals(1, va.getValueCount()); |
| //assertEquals(2, oa.getValueCount()); |
| //assertEquals(2, ia.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(2, oa.get(1)); |
| |
| tp.copyValueSafe(1, 1); |
| assertEquals(0, oa.get(0)); |
| assertEquals(2, oa.get(1)); |
| assertEquals(5, oa.get(2)); |
| |
| tp.copyValueSafe(2, 2); |
| assertEquals(2, oa.get(1)); |
| assertEquals(5, oa.get(2)); |
| assertEquals(5, oa.get(3)); |
| |
| vm.setValueCount(3); |
| |
| // v should now be the same as f |
| validateFrom(v); |
| } |
| } |
| |
| private class SpecialMapVector extends MapVector { |
| |
| private final RepeatedMapVector v; |
| |
| public SpecialMapVector(RepeatedMapVector v) { |
| super("", null, null); |
| this.v = v; |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public <T extends ValueVector> T addOrGet(String name, MajorType type, Class<T> clazz) { |
| assert clazz == RepeatedMapVector.class; |
| return (T) v; |
| } |
| } |
| |
| @Test |
| public void testRepeatedMapWriter() throws Exception { |
| |
| // The writers will create a nullable int inner vector. |
| |
| MaterializedField field = MaterializedField.create("repeated_map", Types.repeated(TypeProtos.MinorType.MAP)); |
| try (RepeatedMapVector v = new RepeatedMapVector(field, allocator, null)) { |
| |
| SpecialMapVector mapVector = new SpecialMapVector(v); |
| @SuppressWarnings("resource") |
| SingleMapWriter mapRoot = new SingleMapWriter(mapVector, null, false); |
| ListWriter lw = mapRoot.list("repeated_map"); |
| MapWriter mw = lw.map(); |
| |
| // Record 1: [10, 20] |
| |
| lw.setPosition(0); |
| lw.startList(); |
| mw.start(); |
| IntWriter iw = mw.integer("inner"); |
| iw.writeInt(10); |
| mw.end(); |
| mw.start(); |
| iw.writeInt(20); |
| mw.end(); |
| lw.endList(); |
| |
| // Record 2: [30, 40, 50] |
| |
| lw.setPosition(1); |
| lw.startList(); |
| mw.start(); |
| iw.writeInt(30); |
| mw.end(); |
| mw.start(); |
| iw.writeInt(40); |
| mw.end(); |
| mw.start(); |
| iw.writeInt(50); |
| mw.end(); |
| lw.endList(); |
| |
| // Record 3: [] |
| |
| lw.setPosition(2); |
| lw.startList(); |
| lw.endList(); |
| |
| v.getMutator().setValueCount(3); |
| |
| assertEquals(3, v.getAccessor().getValueCount()); |
| UInt4Vector.Accessor oa = v.getOffsetVector().getAccessor(); |
| assertEquals(4, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(2, oa.get(1)); |
| assertEquals(5, oa.get(2)); |
| assertEquals(5, oa.get(3)); |
| assertEquals(0, oa.get(4)); // Past end |
| |
| NullableIntVector inner = v.addOrGet("inner", Types.optional(TypeProtos.MinorType.INT), NullableIntVector.class); |
| UInt1Vector.Accessor ba = inner.getBitsVector().getAccessor(); |
| assertEquals(1, ba.get(0)); |
| assertEquals(1, ba.get(1)); |
| assertEquals(1, ba.get(2)); |
| assertEquals(1, ba.get(3)); |
| assertEquals(1, ba.get(4)); |
| assertEquals(0, ba.get(5)); // Past end |
| |
| NullableIntVector.Accessor ia = inner.getAccessor(); |
| assertEquals(10, ia.get(0)); |
| assertEquals(20, ia.get(1)); |
| assertEquals(30, ia.get(2)); |
| assertEquals(40, ia.get(3)); |
| assertEquals(50, ia.get(4)); |
| } |
| } |
| |
| private RepeatedMapVector buildRepeatedMap() { |
| MaterializedField field = MaterializedField.create("repeated_map", Types.repeated(TypeProtos.MinorType.MAP)); |
| RepeatedMapVector v = new RepeatedMapVector(field, allocator, null); |
| getInner(v); |
| v.allocateNew(5, 10); |
| return v; |
| } |
| |
| private IntVector getInner(RepeatedMapVector v) { |
| return v.addOrGet("inner", Types.required(TypeProtos.MinorType.INT), IntVector.class); |
| } |
| |
| private RepeatedMapVector buildFromMap() { |
| RepeatedMapVector v = buildRepeatedMap(); |
| |
| // Can't figure out how to get the indexes to step for |
| // inner and outer values. |
| // If we get the int writer from the repeated map writer, |
| // the column will be converted to nullable int. |
| |
| IntVector iv = getInner(v); |
| IntVector.Mutator im = iv.getMutator(); |
| UInt4Vector ov = v.getOffsetVector(); |
| UInt4Vector.Mutator om = ov.getMutator(); |
| |
| om.set(0, 0); |
| |
| // Record 1: [10, 20] |
| |
| im.set(0, 10); |
| im.set(1, 20); |
| om.set(1, 2); |
| |
| // Record 2: [30, 40, 50] |
| |
| im.set(2, 30); |
| im.set(3, 40); |
| im.set(4, 50); |
| om.set(2, 5); |
| |
| // Record 3: [] |
| |
| om.set(3, 5); |
| |
| om.setValueCount(4); |
| v.getMutator().setValueCount(3); |
| |
| // Sanity check |
| |
| validateFrom(v); |
| return v; |
| } |
| |
| private void validateFrom(RepeatedMapVector v) { |
| UInt4Vector.Accessor oa = v.getOffsetVector().getAccessor(); |
| assertEquals(3, v.getAccessor().getValueCount()); |
| assertEquals(4, oa.getValueCount()); |
| assertEquals(0, oa.get(0)); |
| assertEquals(2, oa.get(1)); |
| assertEquals(5, oa.get(2)); |
| assertEquals(5, oa.get(3)); |
| |
| IntVector.Accessor ia = getInner(v).getAccessor(); |
| assertEquals(10, ia.get(0)); |
| assertEquals(20, ia.get(1)); |
| assertEquals(30, ia.get(2)); |
| assertEquals(40, ia.get(3)); |
| assertEquals(50, ia.get(4)); |
| } |
| } |