blob: be3c9fa45de1e3527578b5f02df7d9e5c5cd175b [file] [log] [blame]
/*
* 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.carbondata.core.scan.expression;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.carbondata.common.logging.LogServiceFactory;
import org.apache.carbondata.core.constants.CarbonCommonConstants;
import org.apache.carbondata.core.metadata.datatype.DataType;
import org.apache.carbondata.core.scan.expression.conditional.GreaterThanEqualToExpression;
import org.apache.carbondata.core.scan.expression.conditional.GreaterThanExpression;
import org.apache.carbondata.core.scan.expression.conditional.LessThanEqualToExpression;
import org.apache.carbondata.core.scan.expression.conditional.LessThanExpression;
import org.apache.carbondata.core.scan.expression.logical.AndExpression;
import org.apache.carbondata.core.scan.expression.logical.OrExpression;
import org.apache.carbondata.core.scan.expression.logical.RangeExpression;
import org.apache.carbondata.core.scan.expression.logical.TrueExpression;
import org.apache.carbondata.core.scan.filter.intf.ExpressionType;
import static org.apache.carbondata.core.scan.filter.intf.ExpressionType.FALSE;
import static org.apache.carbondata.core.scan.filter.intf.ExpressionType.GREATERTHAN;
import static org.apache.carbondata.core.scan.filter.intf.ExpressionType.GREATERTHAN_EQUALTO;
import static org.apache.carbondata.core.scan.filter.intf.ExpressionType.LESSTHAN;
import static org.apache.carbondata.core.scan.filter.intf.ExpressionType.LESSTHAN_EQUALTO;
import org.apache.log4j.Logger;
public class RangeExpressionEvaluator {
private static final Logger LOG =
LogServiceFactory.getLogService(RangeExpressionEvaluator.class.getName());
private Expression expr;
private Expression srcNode;
private Expression tarNode;
private Expression tarParentNode;
public RangeExpressionEvaluator(Expression expr) {
this.expr = expr;
}
public Expression getExpr() {
return expr;
}
public void setExpr(Expression expr) {
this.expr = expr;
}
private Expression getSrcNode() {
return srcNode;
}
private void setTarNode(Expression expr) {
this.tarNode = expr;
}
private void setTarParentNode(Expression expr) {
this.tarParentNode = expr;
}
/**
* This method evaluates is any greater than or less than expression can be transformed
* into a single RANGE filter.
*/
public void rangeExpressionEvaluatorMapBased() {
// The algorithm :
// Get all the nodes of the Expression Tree and fill it into a MAP.
// The Map structure will be currentNode, ColumnName, LessThanOrGreaterThan, Value, ParentNode
// Group the rows in MAP according to the columns and then evaluate if it can be transformed
// into a RANGE or not.
//
// AND AND
// | |
// / \ / \
// / \ / \
// Less Greater => TRUE Range
// / \ / \ / \
// / \ / \ / \
// a 10 a 5 Less greater
// /\ /\
// / \ / \
// a 10 a 5
//
Map<String, List<FilterModificationNode>> filterExpressionMap;
filterExpressionMap = convertFilterTreeToMap();
replaceWithRangeExpression(filterExpressionMap);
filterExpressionMap.clear();
}
public void replaceWithRangeExpression(
Map<String, List<FilterModificationNode>> filterExpressionMap) {
List<FilterModificationNode> deleteExp = new ArrayList<>();
Iterator<Map.Entry<String, List<FilterModificationNode>>> iterator =
filterExpressionMap.entrySet().iterator();
Map.Entry<String, List<FilterModificationNode>> nextEntry = null;
while (iterator.hasNext()) {
nextEntry = iterator.next();
List<FilterModificationNode> filterExp = nextEntry.getValue();
if (filterExp.size() > 1) {
// There are multiple Expression for the same column traverse and check if they can
// form a range.
FilterModificationNode startMin = null;
FilterModificationNode endMax = null;
for (FilterModificationNode exp : filterExp) {
if ((exp.getExpType() == GREATERTHAN) || (exp.getExpType() == GREATERTHAN_EQUALTO)) {
if ((null == endMax) || ((checkLiteralValue(exp.getCurrentExp(),
endMax.getCurrentExp())))) {
if (null == startMin) {
startMin = exp;
} else {
// There is already some value in startMin so check which one is greater.
LiteralExpression srcLiteral = getChildLiteralExpression(startMin.getCurrentExp());
LiteralExpression tarLiteral = getChildLiteralExpression(exp.getCurrentExp());
ExpressionResult srcExpResult = srcLiteral.evaluate(null);
ExpressionResult tarExpResult = tarLiteral.evaluate(null);
if (srcExpResult.compareTo(tarExpResult) < 0) {
// Before replacing the startMin add the current StartMin into deleteExp List
// as they will be replaced with TRUE expression after RANGE formation.
deleteExp.add(startMin);
startMin = exp;
}
}
}
}
if ((exp.getExpType() == LESSTHAN) || (exp.getExpType() == LESSTHAN_EQUALTO)) {
if ((null == startMin) || ((checkLiteralValue(exp.getCurrentExp(),
startMin.getCurrentExp())))) {
if (null == endMax) {
endMax = exp;
} else {
// There is already some value in endMax so check which one is less.
LiteralExpression srcLiteral = getChildLiteralExpression(endMax.getCurrentExp());
LiteralExpression tarLiteral = getChildLiteralExpression(exp.getCurrentExp());
ExpressionResult srcExpResult = srcLiteral.evaluate(null);
ExpressionResult tarExpResult = tarLiteral.evaluate(null);
if (srcExpResult.compareTo(tarExpResult) > 0) {
// Before replacing the endMax add the current endMax into deleteExp List
// as they will be replaced with TRUE expression after RANGE formation.
deleteExp.add(endMax);
endMax = exp;
}
}
}
}
}
if ((null != startMin) && (null != endMax)) {
LOG.info(
"GreaterThan and LessThan Filter Expression changed to Range Expression for column "
+ nextEntry.getKey());
// the node can be converted to RANGE.
Expression n1 = startMin.getCurrentExp();
Expression n2 = endMax.getCurrentExp();
RangeExpression rangeTree = new RangeExpression(n1, n2);
Expression srcParentNode = startMin.getParentExp();
Expression tarParentNode = endMax.getParentExp();
srcParentNode.findAndSetChild(startMin.getCurrentExp(), rangeTree);
tarParentNode.findAndSetChild(endMax.getCurrentExp(), new TrueExpression(null));
if (deleteExp.size() > 0) {
// There are some expression to Delete as they are Redundant after Range Formation.
for (FilterModificationNode trueExp : deleteExp) {
trueExp.getParentExp()
.findAndSetChild(trueExp.getCurrentExp(), new TrueExpression(null));
}
}
}
}
}
}
private Map<String, List<FilterModificationNode>> convertFilterTreeToMap() {
// Traverse the Filter Tree and add the nodes in filterExpressionMap.
// Only those nodes will be added which has got LessThan, LessThanEqualTo
// GreaterThan, GreaterThanEqualTo Expression Only.
Map<String, List<FilterModificationNode>> filterExpressionMap =
new HashMap<>(CarbonCommonConstants.DEFAULT_COLLECTION_SIZE);
// Traverse the Tree.
fillExpressionMap(filterExpressionMap, null, null);
return filterExpressionMap;
}
private void evaluateOrExpression(Expression currentNode, Expression orExpChild) {
Map<String, List<FilterModificationNode>> filterExpressionMapNew =
new HashMap<>(CarbonCommonConstants.DEFAULT_COLLECTION_SIZE);
fillExpressionMap(filterExpressionMapNew, orExpChild, currentNode);
replaceWithRangeExpression(filterExpressionMapNew);
filterExpressionMapNew.clear();
}
private void fillExpressionMap(Map<String, List<FilterModificationNode>> filterExpressionMap,
Expression currentNode, Expression parentNode) {
if (null == currentNode) {
currentNode = this.getExpr();
parentNode = currentNode;
}
// if the parentNode is a ANDExpression and the current node is LessThan, GreaterThan
// then add the node into filterExpressionMap.
if ((parentNode instanceof AndExpression) && (isLessThanGreaterThanExp(currentNode)
&& eligibleForRangeExpConversion(currentNode))) {
addFilterExpressionMap(filterExpressionMap, currentNode, parentNode);
}
// In case of Or Exp we have to evaluate both the subtrees of expression separately
// else it will combine the results of both the subtrees into one expression
// which wont give us correct result
if (currentNode instanceof OrExpression) {
Expression leftChild = ((OrExpression) currentNode).left;
Expression rightChild = ((OrExpression) currentNode).right;
if (null != leftChild) {
evaluateOrExpression(currentNode, leftChild);
}
if (null != rightChild) {
evaluateOrExpression(currentNode, rightChild);
}
} else {
for (Expression exp : currentNode.getChildren()) {
if (null != exp) {
fillExpressionMap(filterExpressionMap, exp, currentNode);
}
}
}
}
private void addFilterExpressionMap(Map<String, List<FilterModificationNode>> filterExpressionMap,
Expression currentNode, Expression parentNode) {
String colName = getColumnName(currentNode);
ExpressionType expType = getExpressionType(currentNode);
FilterModificationNode filterExpression =
new FilterModificationNode(currentNode, parentNode, expType);
if (null == filterExpressionMap.get(colName)) {
filterExpressionMap.put(colName, new ArrayList<FilterModificationNode>());
}
filterExpressionMap.get(colName).add(filterExpression);
}
/**
* This method checks if the Expression is among LessThan, LessThanEqualTo,
* GreaterThan or GreaterThanEqualTo
*
* @param expr
* @return
*/
private boolean isLessThanGreaterThanExp(Expression expr) {
return (expr instanceof LessThanEqualToExpression) || (expr instanceof LessThanExpression)
|| (expr instanceof GreaterThanEqualToExpression)
|| (expr instanceof GreaterThanExpression);
}
/**
* This method verifies if the Expression is qualified for Range Expression conversion.
*
* @param expChild
* @return
*/
private boolean eligibleForRangeExpConversion(Expression expChild) {
for (Expression exp : expChild.getChildren()) {
if (exp instanceof ColumnExpression) {
return ((ColumnExpression) exp).isDimension() &&
!(((ColumnExpression) exp).getDimension().getDataType().isComplexType());
}
}
return false;
}
/**
* This method returns the Column name from the ColumnExpression ExpressionType.
*
* @param andNode
* @return
*/
private String getColumnName(Expression andNode) {
// returns the Column Name from Column Expression.
for (Expression exp : andNode.getChildren()) {
if (exp instanceof ColumnExpression) {
return ((ColumnExpression) exp).getColumnName();
}
}
return null;
}
/**
* This method returns the Value from the Literal ExpressionType.
*
* @param exp
* @return
*/
private Object getLiteralValue(Expression exp) {
for (Expression expr : exp.getChildren()) {
if (expr instanceof LiteralExpression) {
return (((LiteralExpression) expr).getLiteralExpValue());
}
}
return null;
}
/**
* This method returns the DataType of the Literal Expression.
*
* @param exp
* @return
*/
private DataType getLiteralDataType(Expression exp) {
for (Expression expr : exp.getChildren()) {
if (expr instanceof LiteralExpression) {
return (((LiteralExpression) expr).getLiteralExpDataType());
}
}
return null;
}
/**
* This method returns the DataType of the Literal Expression.
*
* @param exp
* @return
*/
private LiteralExpression getChildLiteralExpression(Expression exp) {
for (Expression expr : exp.getChildren()) {
if (expr instanceof LiteralExpression) {
return ((LiteralExpression) expr);
}
}
return null;
}
/**
* This method returns the ExpressionType based on the Expression.
*
* @param exp
* @return
*/
private ExpressionType getExpressionType(Expression exp) {
// return the expressionType. Note among the children of the
// andNode one should be columnExpression others should be
// LessThan, LessThanEqualTo, GreaterThan, GreaterThanEqualTo.
//
if (exp instanceof LessThanExpression) {
return LESSTHAN;
} else if (exp instanceof LessThanEqualToExpression) {
return LESSTHAN_EQUALTO;
} else if (exp instanceof GreaterThanExpression) {
return GREATERTHAN;
} else if (exp instanceof GreaterThanEqualToExpression) {
return GREATERTHAN_EQUALTO;
} else {
return FALSE;
}
}
/**
* This method checks if the Source Expression matches with Target Expression.
*
* @param src
* @param tar
* @return
*/
private boolean matchExpType(ExpressionType src, ExpressionType tar) {
return (((src == LESSTHAN) || (src == LESSTHAN_EQUALTO)) && ((tar == GREATERTHAN) || (tar
== GREATERTHAN_EQUALTO))) || (((src == GREATERTHAN) || (src == GREATERTHAN_EQUALTO)) && (
(tar == LESSTHAN) || (tar == LESSTHAN_EQUALTO)));
}
/**
* This Method Traverses the Expression Tree to find the corresponding node of the Range
* Expression. If one node of Range Expression is LessThan then a corresponding GreaterThan
* will be chosen or vice versa.
*
* @param currentNode
* @param parentNode
* @return
*/
private Expression traverseTree(Expression currentNode, Expression parentNode) {
Expression result = null;
if (null == parentNode) {
currentNode = this.getExpr();
parentNode = currentNode;
}
if (!this.getSrcNode().equals(currentNode) && isLessThanGreaterThanExp(currentNode)) {
String srcColumnName = getColumnName(this.getSrcNode());
String tarColumnName = getColumnName(currentNode);
ExpressionType srcExpType = getExpressionType(this.getSrcNode());
ExpressionType tarExpType = getExpressionType(currentNode);
if ((null != srcColumnName) && (null != tarColumnName) && (srcColumnName
.equals(tarColumnName)) && (srcExpType != ExpressionType.FALSE) && (tarExpType
!= ExpressionType.FALSE) && ((matchExpType(srcExpType, tarExpType)) && checkLiteralValue(
this.getSrcNode(), currentNode))) {
this.setTarNode(currentNode);
this.setTarParentNode(parentNode);
return parentNode;
}
}
for (Expression exp : currentNode.getChildren()) {
if (null != exp && !(exp instanceof RangeExpression)) {
result = traverseTree(exp, currentNode);
if (null != result) {
return result;
}
}
}
return null;
}
/**
* This method will check if the literal values attached to GreaterThan of GreaterThanEqualTo
* is less or equal to LessThan and LessThanEqualTo literal.
*
* @param src
* @param tar
* @return
*/
private boolean checkLiteralValue(Expression src, Expression tar) {
ExpressionType srcExpressionType = getExpressionType(src);
ExpressionType tarExpressionType = getExpressionType(tar);
LiteralExpression srcLiteral = getChildLiteralExpression(src);
LiteralExpression tarLiteral = getChildLiteralExpression(tar);
ExpressionResult srcExpResult = srcLiteral.evaluate(null);
ExpressionResult tarExpResult = tarLiteral.evaluate(null);
switch (srcExpressionType) {
case LESSTHAN:
case LESSTHAN_EQUALTO:
switch (tarExpressionType) {
case GREATERTHAN:
if (srcExpResult.compareTo(tarExpResult) > 0) {
return true;
}
break;
case GREATERTHAN_EQUALTO:
if (srcExpResult.compareTo(tarExpResult) >= 0) {
return true;
}
break;
}
break;
case GREATERTHAN:
case GREATERTHAN_EQUALTO:
switch (tarExpressionType) {
case LESSTHAN:
if (srcExpResult.compareTo(tarExpResult) < 0) {
return true;
}
break;
case LESSTHAN_EQUALTO:
if (srcExpResult.compareTo(tarExpResult) <= 0) {
return true;
}
break;
}
break;
}
return false;
}
}