/**
* 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.broker.requesthandler;
import com.linkedin.pinot.common.request.FilterOperator;
import com.linkedin.pinot.common.utils.request.FilterQueryTree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Optimizer that flattens nested logical operators of the same kind. For example, AND( a, AND (b, c)) is the same as
* AND(a, b, c).
*/
public class FlattenNestedPredicatesFilterQueryTreeOptimizer extends FilterQueryTreeOptimizer {
public static int MAX_OPTIMIZING_DEPTH = 5;
@Override
public FilterQueryTree optimize(FilterQueryOptimizerRequest request) {
FilterQueryTree filterQueryTree = request.getFilterQueryTree();
flatten(filterQueryTree, null, MAX_OPTIMIZING_DEPTH);
return filterQueryTree;
}
/**
* Flatten the operators if parent and child have the same AND or OR operator.
* (e.g. AND( a, AND (b, c)) is the same as AND(a, b, c). This helps when we re-order
* operators for performance.
*
* It does so by looking at the operator of the 'parent' and 'node'. If they are same, and
* collapsible, then all the children of 'node' are moved one level up to be siblings of
* 'node', rendering 'node' childless. 'node' is then removed from 'parent's children list.
*
* @param node The node whose children are to be moved up one level if criteria is satisfied.
* @param parent Node's parent who will inherit node's children if criteria is satisfied.
* @param maxDepth is the maximum depth to which we recurse
*/
private void flatten(FilterQueryTree node, FilterQueryTree parent, int maxDepth) {
if (node == null || node.getChildren() == null || maxDepth == 0) {
return;
}
// Flatten all the children first.
List<FilterQueryTree> toFlatten = new ArrayList<>(node.getChildren().size());
for (FilterQueryTree child : node.getChildren()) {
if (child.getChildren() != null && !child.getChildren().isEmpty()) {
toFlatten.add(child);
}
}
for (FilterQueryTree child : toFlatten) {
flatten(child, node, maxDepth - 1);
}
if (parent == null) {
return;
}
if (node.getOperator() == parent.getOperator()
&& (node.getOperator() == FilterOperator.OR || node.getOperator() == FilterOperator.AND)) {
// Move all of 'node's children one level up. If 'node' has no children left, remove it from parent's list.
List<FilterQueryTree> children = node.getChildren();
Iterator<FilterQueryTree> it = children.iterator();
while (it.hasNext()) {
parent.getChildren().add(it.next());
it.remove();
}
// 'node' is now childless
// Remove this node from its parent's list.
parent.getChildren().remove(node);
}
}
}