blob: 51bc918e22c63065822cbc528906c4730b246573 [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.drill.exec.planner.index;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexFieldAccess;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexLocalRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexRangeRef;
import org.apache.calcite.rex.RexVisitorImpl;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.drill.common.expression.LogicalExpression;
import org.apache.drill.exec.planner.logical.DrillOptiq;
import org.apache.drill.exec.planner.logical.DrillParseContext;
import org.apache.drill.exec.planner.physical.PrelUtil;
import java.util.Map;
import java.util.Set;
/**
* The filter expressions that could be indexed
* Other than SchemaPaths, which represent columns of a table and could be indexed,
* we consider only function expressions, and specifically, CAST function.
* To judge if an expression is indexable, we check these:
* 1, this expression should be one operand of a comparison operator, one of SqlKind.COMPARISON:
* IN, EQUALS, NOT_EQUALS, LESS_THAN, GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL
* 2, the expression tree should contain at least one inputRef (which means this expression is a
* computation on top of at least one column), and if we have more than one indexable expressions
* are found from operands of comparison operator, we should not take any expression as indexable.
*
* 3, (LIMIT to one level function) the expression is a function call, and no nested function call underneath, except ITEM
* 4, (LIMIT to CAST), the function call is a CAST
*/
public class IndexableExprMarker extends RexVisitorImpl<Boolean> {
//map of rexNode->converted LogicalExpression
final Map<RexNode, LogicalExpression> desiredExpressions = Maps.newHashMap();
//the expressions in equality comparison
final Map<RexNode, LogicalExpression> equalityExpressions = Maps.newHashMap();
//the expression found in non-equality comparison
final Map<RexNode, LogicalExpression> notInEquality = Maps.newHashMap();
//for =(cast(a.b as VARCHAR(len)), 'abcd'), if the 'len' is less than the max length of casted field on index table,
// we want to rewrite it to LIKE(cast(a.b as VARCHAR(len)), 'abcd%')
//map equalOnCastChar: key is the equal operator, value is the operand (cast(a.b as VARCHAR(10)),
final Map<RexNode, LogicalExpression> equalOnCastChar = Maps.newHashMap();
final private RelNode inputRel;
//flag current recursive call state: whether we are on a direct operand of comparison operator
boolean directCompareOp = false;
RexCall contextCall = null;
DrillParseContext parserContext;
public IndexableExprMarker(RelNode inputRel) {
super(true);
this.inputRel = inputRel;
parserContext = new DrillParseContext(PrelUtil.getPlannerSettings(inputRel.getCluster()));
}
public Map<RexNode, LogicalExpression> getIndexableExpression() {
return ImmutableMap.copyOf(desiredExpressions);
}
public Map<RexNode, LogicalExpression> getEqualOnCastChar() {
return ImmutableMap.copyOf(equalOnCastChar);
}
/**
* return the expressions that were only in equality condition _and_ only once. ( a.b = 'value' )
* @return
*/
public Set<LogicalExpression> getExpressionsOnlyInEquality() {
Set<LogicalExpression> onlyInEquality = Sets.newHashSet();
Set<LogicalExpression> notInEqSet = Sets.newHashSet();
Set<LogicalExpression> inEqMoreThanOnce = Sets.newHashSet();
notInEqSet.addAll(notInEquality.values());
for (LogicalExpression expr : equalityExpressions.values()) {
//only process expr that is not in any non-equality condition(!notInEqSet.contains)
if (!notInEqSet.contains(expr)) {
//expr appear in two and more equality conditions should be ignored too
if (inEqMoreThanOnce.contains(expr)) {
continue;
}
//we already have recorded this expr in equality condition, move it to inEqMoreThanOnce
if (onlyInEquality.contains(expr)) {
inEqMoreThanOnce.add(expr);
onlyInEquality.remove(expr);
continue;
}
//finally we could take this expr
onlyInEquality.add(expr);
}
}
return onlyInEquality;
}
@Override
public Boolean visitInputRef(RexInputRef rexInputRef) {
return directCompareOp;
}
public boolean containInputRef(RexNode rex) {
if (rex instanceof RexInputRef) {
return true;
}
if ((rex instanceof RexCall) && "ITEM".equals(((RexCall)rex).getOperator().getName())) {
return true;
}
//TODO: use a visitor search recursively for inputRef, if found one return true
return false;
}
public boolean operandsAreIndexable(RexCall call) {
SqlKind kind = call.getKind();
boolean kindIsRight = (SqlKind.COMPARISON.contains(kind) || kind==SqlKind.LIKE || kind == SqlKind.SIMILAR);
if (!kindIsRight) {
return false;
}
int inputReference = 0;
for (RexNode operand : call.operands) {
//if for this operator, there are two operands and more have inputRef, which means it is something like:
// a.b = a.c, instead of a.b ='hello', so this cannot apply index
if (containInputRef(operand)) {
inputReference++;
if(inputReference>=2) {
return false;
}
}
}
return true;
}
@Override
public Boolean visitCall(RexCall call) {
if (call.getKind() == SqlKind.NOT || call.getKind() == SqlKind.NOT_EQUALS || call.getKind() == SqlKind.NOT_IN) {
// Conditions under NOT are not indexable
return false;
}
if (operandsAreIndexable(call)) {
for (RexNode operand : call.operands) {
directCompareOp = true;
contextCall = call;
boolean markIt = operand.accept(this);
directCompareOp = false;
contextCall = null;
if (markIt) {
LogicalExpression expr = DrillOptiq.toDrill(parserContext, inputRel, operand);
desiredExpressions.put(operand, expr);
if (call.getKind() == SqlKind.EQUALS) {
equalityExpressions.put(operand, expr);
}
else {
notInEquality.put(operand, expr);
}
}
}
return false;
}
//now we are handling a call directly under comparison e.g. <([call], literal)
if (directCompareOp) {
// if it is an item, or CAST function
if ("ITEM".equals(call.getOperator().getName())) {
return directCompareOp;
}
else if (call.getKind() == SqlKind.CAST) {
//For now, we care only direct CAST: CAST's operand is a field(schemaPath),
// either ITEM call(nested name) or inputRef
//cast as char/varchar in equals function
if(contextCall != null && contextCall.getKind() == SqlKind.EQUALS
&& (call.getType().getSqlTypeName()== SqlTypeName.CHAR
|| call.getType().getSqlTypeName()==SqlTypeName.VARCHAR)) {
equalOnCastChar.put(contextCall, DrillOptiq.toDrill(parserContext, inputRel, call));
}
RexNode castOp = call.operands.get(0);
if (castOp instanceof RexInputRef) {
return true;
}
if ((castOp instanceof RexCall) && ("ITEM".equals(((RexCall)castOp).getOperator().getName()))) {
return true;
}
}
}
for (RexNode operand : call.operands) {
boolean bret = operand.accept(this);
}
return false;
}
public Boolean visitLocalRef(RexLocalRef localRef) {
return false;
}
public Boolean visitLiteral(RexLiteral literal) {
return false;
}
public Boolean visitOver(RexOver over) {
return false;
}
public Boolean visitCorrelVariable(RexCorrelVariable correlVariable) {
return false;
}
public Boolean visitDynamicParam(RexDynamicParam dynamicParam) {
return false;
}
public Boolean visitRangeRef(RexRangeRef rangeRef) {
return false;
}
public Boolean visitFieldAccess(RexFieldAccess fieldAccess) {
final RexNode expr = fieldAccess.getReferenceExpr();
return expr.accept(this);
}
}