| /* |
| * 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.record; |
| |
| import java.util.Arrays; |
| import java.util.BitSet; |
| |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.drill.common.expression.PathSegment; |
| import org.apache.drill.common.types.TypeProtos.MajorType; |
| import org.apache.drill.exec.expr.BasicTypeHelper; |
| import org.apache.drill.exec.vector.ValueVector; |
| |
| import com.carrotsearch.hppc.IntArrayList; |
| import com.google.common.base.Preconditions; |
| |
| /** |
| * Declares a value vector field, providing metadata about the field. |
| * Drives code generation by providing type and other structural |
| * information that determine code structure. |
| */ |
| |
| public class TypedFieldId { |
| |
| private final MajorType finalType; |
| private final MajorType secondaryFinal; |
| private final MajorType intermediateType; |
| private final int[] fieldIds; |
| private final boolean isHyperReader; |
| private final boolean isListVector; |
| private final PathSegment remainder; |
| |
| /** |
| * Used to determine if a dict is placed at specific depth |
| */ |
| private final BitSet dictBitSet; |
| |
| private TypedFieldId(Builder builder) { |
| this.intermediateType = builder.intermediateType; |
| this.finalType = builder.finalType; |
| this.secondaryFinal = builder.secondaryFinal; |
| this.fieldIds = builder.ids.toArray(); |
| this.isHyperReader = builder.hyperReader; |
| this.isListVector = builder.isListVector; |
| this.remainder = builder.remainder; |
| this.dictBitSet = builder.dictBitSet; |
| } |
| |
| public TypedFieldId cloneWithChild(int id) { |
| int[] fieldIds = ArrayUtils.add(this.fieldIds, id); |
| return getBuilder().clearAndAddIds(fieldIds) |
| .build(); |
| } |
| |
| private Builder getBuilder() { |
| return new Builder().intermediateType(intermediateType) |
| .finalType(finalType) |
| .secondaryFinal(secondaryFinal) |
| .addIds(fieldIds) |
| .remainder(remainder) |
| .copyDictBitSet(dictBitSet) |
| .hyper(isHyperReader) |
| .listVector(isListVector); |
| } |
| |
| public PathSegment getLastSegment() { |
| if (remainder == null) { |
| return null; |
| } |
| PathSegment seg = remainder; |
| while (seg.getChild() != null) { |
| seg = seg.getChild(); |
| } |
| return seg; |
| } |
| |
| public TypedFieldId cloneWithRemainder(PathSegment remainder) { |
| return getBuilder().remainder(remainder) |
| .build(); |
| } |
| |
| public boolean hasRemainder() { |
| return remainder != null; |
| } |
| |
| public PathSegment getRemainder() { |
| return remainder; |
| } |
| |
| public boolean isHyperReader() { |
| return isHyperReader; |
| } |
| |
| public boolean isListVector() { |
| return isListVector; |
| } |
| |
| public MajorType getIntermediateType() { |
| return intermediateType; |
| } |
| |
| /** |
| * Check if it is a {@link org.apache.drill.common.types.TypeProtos.MinorType#DICT} type at a given segment's depth |
| * |
| * @param depth depth of interest starting with {@literal 0} |
| * @return {@code true} if it is DICT, {@code false} otherwise |
| */ |
| public boolean isDict(int depth) { |
| return dictBitSet.get(depth); |
| } |
| |
| /** |
| * Return the class for the value vector (type, mode). |
| * |
| * @return the specific, generated ValueVector subclass that |
| * stores values of the given (type, mode) combination |
| */ |
| |
| public Class<? extends ValueVector> getIntermediateClass() { |
| return BasicTypeHelper.getValueVectorClass(intermediateType.getMinorType(), intermediateType.getMode()); |
| } |
| |
| public MajorType getFinalType() { |
| return finalType; |
| } |
| |
| public int[] getFieldIds() { |
| return fieldIds; |
| } |
| |
| public MajorType getSecondaryFinal() { |
| return secondaryFinal; |
| } |
| |
| public static Builder newBuilder() { |
| return new Builder(); |
| } |
| |
| public static class Builder { |
| final IntArrayList ids = new IntArrayList(); |
| MajorType finalType; |
| MajorType intermediateType; |
| MajorType secondaryFinal; |
| PathSegment remainder; |
| boolean hyperReader; |
| boolean withIndex; |
| boolean isListVector; |
| BitSet dictBitSet = new BitSet(); |
| |
| public Builder addId(int id) { |
| ids.add(id); |
| return this; |
| } |
| |
| public Builder withIndex() { |
| withIndex = true; |
| return this; |
| } |
| |
| public Builder remainder(PathSegment remainder) { |
| this.remainder = remainder; |
| return this; |
| } |
| |
| public Builder hyper(boolean hyper) { |
| this.hyperReader = hyper; |
| return this; |
| } |
| |
| public Builder listVector(boolean listVector) { |
| this.isListVector = listVector; |
| return this; |
| } |
| |
| public Builder finalType(MajorType finalType) { |
| this.finalType = finalType; |
| return this; |
| } |
| |
| public Builder secondaryFinal(MajorType secondaryFinal) { |
| this.secondaryFinal = secondaryFinal; |
| return this; |
| } |
| |
| public Builder intermediateType(MajorType intermediateType) { |
| this.intermediateType = intermediateType; |
| return this; |
| } |
| |
| public Builder setDict(int depth) { |
| this.dictBitSet.set(depth, true); |
| return this; |
| } |
| |
| public Builder resetDictBitSet() { |
| dictBitSet.clear(); |
| return this; |
| } |
| |
| private Builder addIds(int... ids) { |
| for (int id : ids) { |
| this.ids.add(id); |
| } |
| return this; |
| } |
| |
| private Builder clearAndAddIds(int[] ids) { |
| this.ids.clear(); |
| addIds(ids); |
| return this; |
| } |
| |
| private Builder copyDictBitSet(BitSet dictBitSet) { |
| this.dictBitSet.or(dictBitSet); |
| return this; |
| } |
| |
| public TypedFieldId build() { |
| Preconditions.checkNotNull(finalType); |
| |
| if (intermediateType == null) { |
| intermediateType = finalType; |
| } |
| |
| if (secondaryFinal == null) { |
| secondaryFinal = finalType; |
| } |
| |
| //MajorType actualFinalType = finalType; |
| //MajorType secondaryFinal = finalType; |
| |
| // if this has an index, switch to required type for output |
| //if(withIndex && intermediateType == finalType) actualFinalType = finalType.toBuilder().setMode(DataMode.REQUIRED).build(); |
| |
| // if this isn't a direct access, switch the final type to nullable as offsets may be null. |
| // TODO: there is a bug here with some things. |
| //if(intermediateType != finalType) actualFinalType = finalType.toBuilder().setMode(DataMode.OPTIONAL).build(); |
| |
| return new TypedFieldId(this); |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + Arrays.hashCode(fieldIds); |
| result = prime * result + ((finalType == null) ? 0 : finalType.hashCode()); |
| result = prime * result + ((intermediateType == null) ? 0 : intermediateType.hashCode()); |
| result = prime * result + (isHyperReader ? 1231 : 1237); |
| result = prime * result + ((remainder == null) ? 0 : remainder.hashCode()); |
| result = prime * result + ((secondaryFinal == null) ? 0 : secondaryFinal.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| TypedFieldId other = (TypedFieldId) obj; |
| if (!Arrays.equals(fieldIds, other.fieldIds)) { |
| return false; |
| } |
| if (finalType == null) { |
| if (other.finalType != null) { |
| return false; |
| } |
| } else if (!finalType.equals(other.finalType)) { |
| return false; |
| } |
| if (intermediateType == null) { |
| if (other.intermediateType != null) { |
| return false; |
| } |
| } else if (!intermediateType.equals(other.intermediateType)) { |
| return false; |
| } |
| if (isHyperReader != other.isHyperReader) { |
| return false; |
| } |
| if (remainder == null) { |
| if (other.remainder != null) { |
| return false; |
| } |
| } else if (!remainder.equals(other.remainder)) { |
| return false; |
| } |
| if (secondaryFinal == null) { |
| if (other.secondaryFinal != null) { |
| return false; |
| } |
| } else if (!secondaryFinal.equals(other.secondaryFinal)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| final int maxLen = 10; |
| return "TypedFieldId [fieldIds=" |
| + (fieldIds != null ? Arrays.toString(Arrays.copyOf(fieldIds, Math.min(fieldIds.length, maxLen))) : null) |
| + ", remainder=" + remainder + "]"; |
| } |
| |
| /** |
| * Generates the full path to a field from the typefield ids |
| * |
| * @param typeFieldId |
| * @param recordBatch |
| * @return |
| */ |
| public static String getPath(TypedFieldId typeFieldId, RecordBatch recordBatch) { |
| StringBuilder name = new StringBuilder(); |
| final String SEPARATOR = "."; |
| final int[] fieldIds = typeFieldId.getFieldIds(); |
| VectorWrapper<?> topLevel = recordBatch.getValueAccessorById(null, fieldIds[0]); |
| name.append(topLevel.getField().getName()); |
| // getChildWrapper(int[] fieldIds) is used to walk down the list of fieldIds. |
| // getChildWrapper() has a quirk where if the fieldIds array is of length == 1 |
| // then it would just return 'this'. |
| // For example, if you had a field 'a.b' with field ids {1, 2} and you had the |
| // VectorWrapper for 'a', say 'aVW'. Then calling aVW.getChildWrapper({2}) returns |
| // aVW and not the vectorWrapper for 'b'. |
| // The code works around this quirk by always querying 2 levels deep. |
| // i.e. childVectorWrapper = parentVectorWrapper.gerChildWrapper({parentFieldId, childFieldId}) |
| int[] lookupLevel = new int[2]; |
| for (int i = 0; i < fieldIds.length - 1; i++) { |
| lookupLevel[0] = fieldIds[i]; |
| // this is the level for which the actual lookup is done |
| lookupLevel[1] = fieldIds[i + 1]; |
| topLevel = topLevel.getChildWrapper(lookupLevel); |
| name.append(SEPARATOR + topLevel.getField().getName()); |
| } |
| return name.toString(); |
| } |
| |
| } |