/** * Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com) * * Licensed 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 com.linkedin.pinot.core.startree; import com.google.common.base.Objects; import com.linkedin.pinot.common.data.FieldSpec; import java.io.Serializable; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; public class StarTreeIndexNode implements StarTreeIndexNodeInterf, Serializable { private static final long serialVersionUID = 1; private static final int GROUP = -2; private int nodeId; private int level; private int dimensionName; private int dimensionValue; private int childDimensionName = -1; private Map<Integer, StarTreeIndexNode> children; private StarTreeIndexNode parent; private int startDocumentId = -1; private int endDocumentId = -1; // materialized document id that contains aggregated data for this node private int aggregatedDocumentId = -1; /** * An element in the StarTreeIndex. */ public StarTreeIndexNode() { } public int getNodeId() { return nodeId; } public void setNodeId(int nodeId) { this.nodeId = nodeId; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } @Override public int getDimensionName() { return dimensionName; } @Override public void setDimensionName(int dimension) { this.dimensionName = dimension; } @Override public int getDimensionValue() { return dimensionValue; } @Override public void setDimensionValue(int dimensionValue) { this.dimensionValue = dimensionValue; } @Override public int getChildDimensionName() { return childDimensionName; } @Override public int getStartDocumentId() { return startDocumentId; } @Override public void setStartDocumentId(int docStartId) { this.startDocumentId = docStartId; } public void setChildDimensionName(int childDimensionName) { this.childDimensionName = childDimensionName; } public Map<Integer, StarTreeIndexNode> getChildren() { return children; } @Override public Iterator<StarTreeIndexNode> getChildrenIterator() { return (children != null) ? children.values().iterator() : Collections.<StarTreeIndexNode>emptyIterator(); } @Override public boolean isLeaf() { return children == null; } @Override public StarTreeIndexNodeInterf getChildForDimensionValue(int dimensionValue) { if (children != null && children.containsKey(dimensionValue)) { return children.get(dimensionValue); } else { return null; } } public void addChild(StarTreeIndexNodeInterf child, int dimensionValue) { children.put(dimensionValue, (StarTreeIndexNode) child); } public void setChildren(Map<Integer, StarTreeIndexNode> children) { this.children = children; } public StarTreeIndexNode getParent() { return parent; } public void setParent(StarTreeIndexNode parent) { this.parent = parent; } public int getEndDocumentId() { return endDocumentId; } public void setEndDocumentId(int endDocumentId) { this.endDocumentId = endDocumentId; } public void setAggregatedDocumentId(int aggregatedDocumentId) { this.aggregatedDocumentId = aggregatedDocumentId; } @Override public int getNumChildren() { return (children != null) ? children.size() : 0; } public int getAggregatedDocumentId() { return aggregatedDocumentId; } @Override public int hashCode() { return Objects.hashCode(nodeId, dimensionName, dimensionValue, childDimensionName, startDocumentId, endDocumentId, aggregatedDocumentId); } @Override public boolean equals(Object o) { if (!(o instanceof StarTreeIndexNode)) { return false; } StarTreeIndexNode n = (StarTreeIndexNode) o; return Objects.equal(nodeId, n.getNodeId()) && Objects.equal(level, n.getLevel()) && Objects.equal(dimensionName, n.getDimensionName()) && Objects.equal(dimensionValue, n.getDimensionValue()) && Objects.equal(childDimensionName, n.getChildDimensionName()) && Objects.equal(children, n.getChildren()) && Objects.equal(startDocumentId, n.getStartDocumentId()) && Objects.equal(endDocumentId, n.getEndDocumentId()) && Objects.equal(aggregatedDocumentId, n.getAggregatedDocumentId()); } @Override public String toString() { return Objects.toStringHelper(this).add("nodeId", nodeId).add("level", level) .add("dimensionName", dimensionName).add("dimensionValue", dimensionValue) .add("childDimensionName", childDimensionName) .add("childCount", children == null ? 0 : children.size()) .add("startDocumentId", startDocumentId).add("endDocumentId", endDocumentId) .add("documentCount", (endDocumentId - startDocumentId)) .add("aggregatedDocumentId", aggregatedDocumentId).toString(); } /** * Returns the dimension IDs, in order of tree level, to this node. */ public List<Integer> getPathDimensions() { StarTreeIndexNode current = this; List<Integer> dimensions = new LinkedList<Integer>(); while (current != null && current.getParent() != null) { dimensions.add(0, current.getDimensionName()); current = current.getParent(); } return dimensions; } /** * Returns the dimension values, in order of tree level, to this node. */ public Map<Integer, Integer> getPathValues() { StarTreeIndexNode current = this; Map<Integer, Integer> values = new HashMap<Integer, Integer>(); while (current != null && current.getParent() != null) { values.put(current.getDimensionName(), current.getDimensionValue()); current = current.getParent(); } return values; } /** * Returns the child node that matches dimensions, or null if none matches. */ public StarTreeIndexNode getMatchingNode(List<Integer> dimensions) { return getMatchingNode(this, dimensions); } public StarTreeIndexNode getMatchingNode(StarTreeIndexNode node, List<Integer> dimensions) { if (node == null || node.isLeaf()) { return node; } Integer childDimensionName = node.getChildDimensionName(); Integer childDimensionValue = dimensions.get(childDimensionName); StarTreeIndexNode child = node.getChildren().get(childDimensionValue); return getMatchingNode(child, dimensions); } public static int group() { return GROUP; } /** Returns true if the dimension combination has the specified prefix */ public static boolean matchesPrefix(Map<Integer, Integer> prefix, List<Integer> combination) { for (Map.Entry<Integer, Integer> entry : prefix.entrySet()) { Integer index = entry.getKey(); Integer value = entry.getValue(); if (!value.equals(combination.get(index))) { return false; } } return true; } public static void printTree(StarTreeIndexNode node, int level) { for (int i = 0; i < level; i++) { System.out.print(" "); } System.out.println(node); if (!node.isLeaf()) { for (StarTreeIndexNode child : node.getChildren().values()) { printTree(child, level + 1); } } } public static Object getAllValue(FieldSpec spec) { Object allValue; switch (spec.getDataType()) { case INT: allValue = spec.getDefaultNullValue(); break; case LONG: allValue = spec.getDefaultNullValue(); break; case FLOAT: allValue = spec.getDefaultNullValue(); break; case DOUBLE: allValue = spec.getDefaultNullValue(); break; case STRING: case BOOLEAN: allValue = spec.getDefaultNullValue(); break; default: throw new UnsupportedOperationException( "unsupported data type : " + spec.getDataType() + " : " + " for column : " + spec.getName()); } return allValue; } }