| /** |
| * 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.tajo.engine.planner.physical; |
| |
| import com.google.common.primitives.Booleans; |
| import com.google.common.primitives.Doubles; |
| import com.google.common.primitives.Floats; |
| import com.google.common.primitives.Ints; |
| import com.google.common.primitives.Longs; |
| import com.google.common.primitives.Shorts; |
| import com.google.common.primitives.UnsignedInts; |
| import org.apache.tajo.catalog.Schema; |
| import org.apache.tajo.catalog.SortSpec; |
| import org.apache.tajo.common.TajoDataTypes; |
| import org.apache.tajo.datum.Datum; |
| import org.apache.tajo.datum.DatumFactory; |
| import org.apache.tajo.datum.NullDatum; |
| import org.apache.tajo.datum.TextDatum; |
| import org.apache.tajo.exception.TajoRuntimeException; |
| import org.apache.tajo.exception.UnsupportedException; |
| import org.apache.tajo.storage.Tuple; |
| import org.apache.tajo.storage.VTuple; |
| |
| import java.util.Arrays; |
| import java.util.BitSet; |
| |
| /** |
| * Extract raw level values (primitive or String/byte[]) from each of key columns for compare |
| */ |
| public class ComparableVector { |
| |
| protected final Tuple[] tuples; // source tuples |
| protected final TupleVector[] vectors; // values of key columns |
| protected final int[] keyIndex; |
| |
| public ComparableVector(int length, SortSpec[] sortKeys, int[] keyIndex) { |
| tuples = new Tuple[length]; |
| vectors = new TupleVector[sortKeys.length]; |
| for (int i = 0; i < vectors.length; i++) { |
| TajoDataTypes.Type type = sortKeys[i].getSortKey().getDataType().getType(); |
| boolean nullFirst = sortKeys[i].isNullFirst(); |
| boolean ascending = sortKeys[i].isAscending(); |
| boolean nullInvert = nullFirst && ascending || !nullFirst && !ascending; |
| vectors[i] = new TupleVector(vectorType(type), tuples.length, nullInvert, ascending); |
| } |
| this.keyIndex = keyIndex; |
| } |
| |
| public int compare(final int i1, final int i2) { |
| for (TupleVector vector : vectors) { |
| int compare = vector.compare(i1, i2); |
| if (compare != 0) { |
| return compare; |
| } |
| } |
| return 0; |
| } |
| |
| public void set(int index, Tuple tuple) { |
| for (int i = 0; i < vectors.length; i++) { |
| vectors[i].set(index, tuple, keyIndex[i]); |
| } |
| } |
| |
| protected static class TupleVector { |
| |
| private final int type; |
| private final BitSet nulls; |
| private final boolean nullInvert; |
| private final boolean ascending; |
| |
| private boolean[] booleans; |
| private byte[] bits; |
| private short[] shorts; |
| private int[] ints; |
| private long[] longs; |
| private float[] floats; |
| private double[] doubles; |
| private byte[][] bytes; |
| |
| private int index; |
| |
| private TupleVector(int type, int length, boolean nullInvert, boolean ascending) { |
| this.type = type; |
| this.nulls = new BitSet(length); |
| this.nullInvert = nullInvert; |
| this.ascending = ascending; |
| switch (type) { |
| case 0: booleans = new boolean[length]; break; |
| case 1: bits = new byte[length]; break; |
| case 2: shorts = new short[length]; break; |
| case 3: ints = new int[length]; break; |
| case 4: longs = new long[length]; break; |
| case 5: floats = new float[length]; break; |
| case 6: doubles = new double[length]; break; |
| case 7: bytes = new byte[length][]; break; |
| case 8: ints = new int[length]; break; |
| case -1: break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| protected final void append(Tuple tuple, int field) { |
| set(index++, tuple, field); |
| } |
| |
| protected final void set(int index, Tuple tuple, int field) { |
| if (tuple.isBlankOrNull(field)) { |
| nulls.set(index); |
| return; |
| } |
| nulls.clear(index); |
| switch (type) { |
| case 0: booleans[index] = tuple.getBool(field); break; |
| case 1: bits[index] = tuple.getByte(field); break; |
| case 2: shorts[index] = tuple.getInt2(field); break; |
| case 3: ints[index] = tuple.getInt4(field); break; |
| case 4: longs[index] = tuple.getInt8(field); break; |
| case 5: floats[index] = tuple.getFloat4(field); break; |
| case 6: doubles[index] = tuple.getFloat8(field); break; |
| case 7: bytes[index] = tuple.getBytes(field); break; |
| case 8: ints[index] = tuple.getInt4(field); break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| |
| protected final int compare(int index1, int index2) { |
| final boolean n1 = nulls.get(index1); |
| final boolean n2 = nulls.get(index2); |
| if (n1 && n2) { |
| return 0; |
| } |
| if (n1 ^ n2) { |
| int compVal = n1 ? 1 : -1; |
| return nullInvert ? -compVal : compVal; |
| } |
| int compare; |
| switch (type) { |
| case 0: compare = Booleans.compare(booleans[index1], booleans[index2]); break; |
| case 1: compare = bits[index1] - bits[index2]; break; |
| case 2: compare = Shorts.compare(shorts[index1], shorts[index2]); break; |
| case 3: compare = Ints.compare(ints[index1], ints[index2]); break; |
| case 4: compare = Longs.compare(longs[index1], longs[index2]); break; |
| case 5: compare = Floats.compare(floats[index1], floats[index2]); break; |
| case 6: compare = Doubles.compare(doubles[index1], doubles[index2]); break; |
| case 7: compare = TextDatum.COMPARATOR.compare(bytes[index1], bytes[index2]); break; |
| case 8: compare = UnsignedInts.compare(ints[index1], ints[index2]); break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| return ascending ? compare : -compare; |
| } |
| } |
| |
| public static class ComparableTuple { |
| |
| private final TupleType[] keyTypes; |
| private final int[] keyIndex; |
| private final Object[] keys; |
| |
| public ComparableTuple(Schema schema, int[] keyIndex) { |
| this(tupleTypes(schema, keyIndex), keyIndex); |
| } |
| |
| public ComparableTuple(Schema schema, int start, int end) { |
| this(schema, toKeyIndex(start, end)); |
| } |
| |
| private ComparableTuple(TupleType[] keyTypes, int[] keyIndex) { |
| this.keyTypes = keyTypes; |
| this.keyIndex = keyIndex; |
| this.keys = new Object[keyIndex.length]; |
| } |
| |
| public int size() { |
| return keyIndex.length; |
| } |
| |
| public void set(Tuple tuple) { |
| for (int i = 0; i < keyTypes.length; i++) { |
| final int field = keyIndex[i]; |
| if (tuple.isBlankOrNull(field)) { |
| keys[i] = null; |
| continue; |
| } |
| switch (keyTypes[i]) { |
| case BOOLEAN: keys[i] = tuple.getBool(field); break; |
| case BIT: keys[i] = tuple.getByte(field); break; |
| case INT1: |
| case INT2: keys[i] = tuple.getInt2(field); break; |
| case INT4: |
| case DATE: |
| case INET4: keys[i] = tuple.getInt4(field); break; |
| case INT8: |
| case TIME: |
| case TIMESTAMP: keys[i] = tuple.getInt8(field); break; |
| case FLOAT4: keys[i] = tuple.getFloat4(field); break; |
| case FLOAT8: keys[i] = tuple.getFloat8(field); break; |
| case TEXT: |
| case CHAR: |
| case BLOB: keys[i] = tuple.getBytes(field); break; |
| case DATUM: keys[i] = tuple.asDatum(field); break; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof ComparableTuple) { |
| ComparableTuple other = (ComparableTuple)obj; |
| for (int i = 0; i < keys.length; i++) { |
| final boolean n1 = keys[i] == null; |
| final boolean n2 = other.keys[i] == null; |
| if (n1 && n2) { |
| continue; |
| } |
| if (n1 ^ n2) { |
| return false; |
| } |
| switch (keyTypes[i]) { |
| case TEXT: |
| case CHAR: |
| case BLOB: if (!Arrays.equals((byte[])keys[i], (byte[])other.keys[i])) return false; continue; |
| default: if (!keys[i].equals(other.keys[i])) return false; continue; |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| public boolean equals(Tuple tuple) { |
| for (int i = 0; i < keys.length; i++) { |
| final int field = keyIndex[i]; |
| final boolean n1 = keys[i] == null; |
| final boolean n2 = tuple.isBlankOrNull(field); |
| if (n1 && n2) { |
| continue; |
| } |
| if (n1 ^ n2) { |
| return false; |
| } |
| switch (keyTypes[i]) { |
| case BOOLEAN: if ((Boolean)keys[i] != tuple.getBool(field)) return false; continue; |
| case BIT: if ((Byte)keys[i] != tuple.getByte(field)) return false; continue; |
| case INT1: |
| case INT2: if ((Short)keys[i] != tuple.getInt2(field)) return false; continue; |
| case INT4: |
| case DATE: |
| case INET4: if ((Integer)keys[i] != tuple.getInt4(field)) return false; continue; |
| case INT8: |
| case TIME: |
| case TIMESTAMP: if ((Long)keys[i] != tuple.getInt8(field)) return false; continue; |
| case FLOAT4: if ((Float)keys[i] != tuple.getFloat4(field)) return false; continue; |
| case FLOAT8: if ((Double)keys[i] != tuple.getFloat8(field)) return false; continue; |
| case TEXT: |
| case CHAR: |
| case BLOB: if (!Arrays.equals((byte[])keys[i], tuple.getBytes(field))) return false; continue; |
| case DATUM: if (!keys[i].equals(tuple.asDatum(field))) return false; continue; |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = 1; |
| for (Object key : keys) { |
| int hash = key == null ? 0 : |
| key instanceof byte[] ? Arrays.hashCode((byte[])key) : key.hashCode(); |
| result = 31 * result + hash; |
| } |
| return result; |
| } |
| |
| public ComparableTuple copy() { |
| ComparableTuple copy = emptyCopy(); |
| System.arraycopy(keys, 0, copy.keys, 0, keys.length); |
| return copy; |
| } |
| |
| public ComparableTuple emptyCopy() { |
| return new ComparableTuple(keyTypes, keyIndex); |
| } |
| |
| public Datum toDatum(int i) { |
| if (keys[i] == null) { |
| return NullDatum.get(); |
| } |
| switch (keyTypes[i]) { |
| case NULL_TYPE: return NullDatum.get(); |
| case BOOLEAN: return DatumFactory.createBool((Boolean) keys[i]); |
| case BIT: return DatumFactory.createBit((Byte)keys[i]); |
| case INT1: |
| case INT2: return DatumFactory.createInt2((Short) keys[i]); |
| case INT4: return DatumFactory.createInt4((Integer) keys[i]); |
| case DATE: return DatumFactory.createDate((Integer) keys[i]); |
| case INET4: return DatumFactory.createInet4((Integer) keys[i]); |
| case INT8: return DatumFactory.createInt8((Long) keys[i]); |
| case TIME: return DatumFactory.createTime((Long) keys[i]); |
| case TIMESTAMP: return DatumFactory.createTimestamp((Long) keys[i]); |
| case FLOAT4: return DatumFactory.createFloat4((Float) keys[i]); |
| case FLOAT8: return DatumFactory.createFloat8((Double) keys[i]); |
| case TEXT: return DatumFactory.createText((byte[]) keys[i]); |
| case CHAR: return DatumFactory.createChar((byte[]) keys[i]); |
| case BLOB: return DatumFactory.createBlob((byte[]) keys[i]); |
| case DATUM: return (Datum)keys[i]; |
| default: |
| throw new IllegalArgumentException(); |
| } |
| } |
| } |
| |
| public static boolean isVectorizable(SortSpec[] sortKeys) { |
| if (sortKeys.length == 0) { |
| return false; |
| } |
| for (SortSpec spec : sortKeys) { |
| try { |
| vectorType(spec.getSortKey().getDataType().getType()); |
| } catch (Exception e) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static int vectorType(TajoDataTypes.Type type) { |
| switch (type) { |
| case BOOLEAN: return 0; |
| case BIT: return 1; |
| case INT1: case INT2: return 2; |
| case INT4: case DATE: return 3; |
| case INT8: case TIME: case TIMESTAMP: case INTERVAL: return 4; |
| case FLOAT4: return 5; |
| case FLOAT8: return 6; |
| case TEXT: case CHAR: case BLOB: return 7; |
| case INET4: return 8; |
| case NULL_TYPE: return -1; |
| default: |
| throw new TajoRuntimeException(new UnsupportedException("data type '" + type.name() + "'")); |
| } |
| |
| } |
| |
| private static TupleType[] tupleTypes(Schema schema, int[] keyIndex) { |
| TupleType[] types = new TupleType[keyIndex.length]; |
| for (int i = 0; i < keyIndex.length; i++) { |
| types[i] = tupleType(schema.getColumn(keyIndex[i]).getDataType().getType()); |
| } |
| return types; |
| } |
| |
| private static TupleType tupleType(TajoDataTypes.Type type) { |
| switch (type) { |
| case BOOLEAN: return TupleType.BOOLEAN; |
| case BIT: return TupleType.BIT; |
| case INT1: return TupleType.INT1; |
| case INT2: return TupleType.INT2; |
| case INT4: return TupleType.INT4; |
| case DATE: return TupleType.DATE; |
| case INT8: return TupleType.INT8; |
| case TIME: return TupleType.TIME; |
| case TIMESTAMP: return TupleType.TIMESTAMP; |
| case FLOAT4: return TupleType.FLOAT4; |
| case FLOAT8: return TupleType.FLOAT8; |
| case TEXT: return TupleType.TEXT; |
| case CHAR: return TupleType.CHAR; |
| case BLOB: return TupleType.BLOB; |
| case INET4: return TupleType.INET4; |
| case NULL_TYPE: return TupleType.NULL_TYPE; |
| default: return TupleType.DATUM; |
| } |
| } |
| |
| private static int[] toKeyIndex(int start, int end) { |
| int[] keyIndex = new int[end - start]; |
| for (int i = 0; i < keyIndex.length; i++) { |
| keyIndex[i] = start + i; |
| } |
| return keyIndex; |
| } |
| |
| private static enum TupleType { |
| NULL_TYPE, BOOLEAN, BIT, INT1, INT2, INT4, DATE, INET4, INT8, TIME, TIMESTAMP, |
| FLOAT4, FLOAT8, TEXT, CHAR, BLOB, DATUM |
| } |
| } |