| /* |
| * 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 org.apache.drill.exec.util.Utilities; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Maps; |
| import org.apache.calcite.rel.RelNode; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeField; |
| import org.apache.calcite.rex.RexBuilder; |
| 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.RexShuttle; |
| import org.apache.calcite.rex.RexVisitorImpl; |
| |
| import org.apache.calcite.sql.fun.SqlStdOperatorTable; |
| import org.apache.calcite.sql.type.SqlTypeName; |
| import org.apache.calcite.util.NlsString; |
| import org.apache.drill.common.expression.LogicalExpression; |
| import org.apache.drill.common.expression.PathSegment; |
| |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Rewrite RexNode with these policies: |
| * 1) field renamed. The input field was named differently in index table, |
| * 2) field is in different position of underlying rowtype |
| * |
| * TODO: 3) certain operator needs rewriting. e.g. CAST function |
| * This class for now applies to only filter on scan, for filter-on-project-on-scan. A stack of |
| * rowType is required. |
| */ |
| public class SimpleRexRemap { |
| final RelNode origRel; |
| final RelDataType origRowType; |
| final RelDataType newRowType; |
| |
| private RexBuilder builder; |
| private Map<LogicalExpression, LogicalExpression> destExprMap; |
| |
| public SimpleRexRemap(RelNode origRel, |
| RelDataType newRowType, RexBuilder builder) { |
| super(); |
| this.origRel = origRel; |
| this.origRowType = origRel.getRowType(); |
| this.newRowType = newRowType; |
| this.builder = builder; |
| this.destExprMap = Maps.newHashMap(); |
| } |
| |
| /** |
| * Set the map of src expression to target expression, expressions not in the map do not have assigned destinations |
| * @param exprMap |
| * @return |
| */ |
| public SimpleRexRemap setExpressionMap(Map<LogicalExpression, LogicalExpression> exprMap) { |
| destExprMap.putAll(exprMap); |
| return this; |
| } |
| |
| public RexNode rewriteEqualOnCharToLike(RexNode expr, |
| Map<RexNode, LogicalExpression> equalOnCastCharExprs) { |
| Map<RexNode, RexNode> srcToReplace = Maps.newIdentityHashMap(); |
| for (Map.Entry<RexNode, LogicalExpression> entry: equalOnCastCharExprs.entrySet()) { |
| RexNode equalOp = entry.getKey(); |
| LogicalExpression opInput = entry.getValue(); |
| |
| final List<RexNode> operands = ((RexCall)equalOp).getOperands(); |
| RexLiteral newLiteral = null; |
| RexNode input = null; |
| if (operands.size() == 2 ) { |
| RexLiteral oplit = null; |
| if (operands.get(0) instanceof RexLiteral) { |
| oplit = (RexLiteral) operands.get(0); |
| if (oplit.getTypeName() == SqlTypeName.CHAR) { |
| newLiteral = builder.makeLiteral(((NlsString) oplit.getValue()).getValue() + "%"); |
| input = operands.get(1); |
| } |
| } |
| else if (operands.get(1) instanceof RexLiteral) { |
| oplit = (RexLiteral) operands.get(1); |
| if (oplit.getTypeName() == SqlTypeName.CHAR) { |
| newLiteral = builder.makeLiteral(((NlsString) oplit.getValue()).getValue() + "%"); |
| input = operands.get(0); |
| } |
| } |
| } |
| if (newLiteral != null) { |
| srcToReplace.put(equalOp, builder.makeCall(SqlStdOperatorTable.LIKE, input, newLiteral)); |
| } |
| } |
| if (srcToReplace.size() > 0) { |
| RexReplace replacer = new RexReplace(srcToReplace); |
| RexNode resultRex = expr.accept(replacer); |
| return resultRex; |
| } |
| return expr; |
| } |
| |
| /** |
| * |
| * @param srcRex the source RexNode to be rewritten |
| * @param mapRexToExpr a map of rex->logical expression to guide what rex to rewrite |
| * @return the RexNode after rewriting |
| */ |
| public RexNode rewriteWithMap(RexNode srcRex, Map<RexNode, LogicalExpression> mapRexToExpr) { |
| Map<RexNode, RexNode> destNodeMap = Maps.newHashMap(); |
| for (Map.Entry<RexNode, LogicalExpression> entry: mapRexToExpr.entrySet()) { |
| LogicalExpression entryExpr = entry.getValue(); |
| |
| LogicalExpression destExpr = destExprMap.get(entryExpr); |
| // then build rexNode from the path |
| RexNode destRex = buildRexForField(destExpr==null?entryExpr : destExpr, newRowType); |
| destNodeMap.put(entry.getKey(), destRex); |
| } |
| |
| // Visit through the nodes, if destExprMap has an entry to provide substitute to replace a rexNode, replace the rexNode |
| RexReplace replacer = new RexReplace(destNodeMap); |
| RexNode resultRex = srcRex.accept(replacer); |
| return resultRex; |
| } |
| |
| public RexNode rewrite(RexNode expr) { |
| IndexableExprMarker marker = new IndexableExprMarker(origRel); |
| expr.accept(marker); |
| return rewriteWithMap(expr, marker.getIndexableExpression()); |
| } |
| |
| private RexNode buildRexForField(LogicalExpression expr, RelDataType newRowType) { |
| ExprToRex toRex = new ExprToRex(origRel, newRowType, builder); |
| return expr.accept(toRex, null); |
| } |
| |
| public static String getFullPath(PathSegment pathSeg) { |
| PathSegment.NameSegment nameSeg = (PathSegment.NameSegment)pathSeg; |
| if (nameSeg.isLastPath()) { |
| return nameSeg.getPath(); |
| } |
| return String.format("%s.%s", |
| nameSeg.getPath(), |
| getFullPath(nameSeg.getChild())); |
| } |
| |
| /** |
| * This class go through the RexNode, collect all the fieldNames, mark starting positions(RexNode) of fields |
| * so this information can be used later e,.g. replaced with a substitute node |
| */ |
| public static class FieldsMarker extends RexVisitorImpl<PathSegment> { |
| final List<String> fieldNames; |
| final List<RelDataTypeField> fields; |
| final Map<RexNode, String> desiredFields = Maps.newHashMap(); |
| |
| int stackDepth; |
| |
| public FieldsMarker(RelDataType rowType) { |
| super(true); |
| this.fieldNames = rowType.getFieldNames(); |
| this.fields = rowType.getFieldList(); |
| this.stackDepth = 0; |
| } |
| |
| private PathSegment newPath(PathSegment segment, RexNode node) { |
| if (stackDepth == 0) { |
| desiredFields.put(node, getFullPath(segment)); |
| } |
| return segment; |
| } |
| |
| private PathSegment newPath(String path, RexNode node) { |
| PathSegment segment = new PathSegment.NameSegment(path); |
| if (stackDepth == 0) { |
| desiredFields.put(node, getFullPath(segment)); |
| } |
| return segment; |
| } |
| |
| public Map<RexNode, String> getFieldAndPos() { |
| return ImmutableMap.copyOf(desiredFields); |
| } |
| |
| @Override |
| public PathSegment visitInputRef(RexInputRef inputRef) { |
| int index = inputRef.getIndex(); |
| String name = fieldNames.get(index); |
| return newPath(name, inputRef); |
| } |
| |
| @Override |
| public PathSegment visitCall(RexCall call) { |
| if ("ITEM".equals(call.getOperator().getName())) { |
| stackDepth++; |
| PathSegment mapOrArray = call.operands.get(0).accept(this); |
| stackDepth--; |
| if (mapOrArray != null) { |
| if (call.operands.get(1) instanceof RexLiteral) { |
| PathSegment newFieldPath = newPath( |
| mapOrArray.cloneWithNewChild(Utilities.convertLiteral((RexLiteral) call.operands.get(1))), |
| call); |
| return newFieldPath; |
| } |
| return mapOrArray; |
| } |
| } else { |
| for (RexNode operand : call.operands) { |
| operand.accept(this); |
| } |
| } |
| return null; |
| } |
| } |
| |
| public static class RexReplace extends RexShuttle { |
| |
| final Map<RexNode, RexNode> rexMap; |
| |
| public RexReplace( Map<RexNode, RexNode> rexMap) { |
| this.rexMap = rexMap; |
| } |
| boolean toReplace(RexNode node) { |
| return rexMap.containsKey(node); |
| } |
| |
| RexNode replace(RexNode node) { |
| return rexMap.get(node); |
| } |
| |
| public RexNode visitOver(RexOver over) { |
| return toReplace(over) ? replace(over) : super.visitOver(over); |
| } |
| |
| public RexNode visitCall(final RexCall call) { |
| return toReplace(call) ? replace(call) : super.visitCall(call); |
| } |
| |
| public RexNode visitCorrelVariable(RexCorrelVariable variable) { |
| return variable; |
| } |
| |
| public RexNode visitFieldAccess(RexFieldAccess fieldAccess) { |
| return toReplace(fieldAccess) ? replace(fieldAccess) : super.visitFieldAccess(fieldAccess); |
| } |
| |
| public RexNode visitInputRef(RexInputRef inputRef) { |
| return toReplace(inputRef) ? replace(inputRef) : super.visitInputRef(inputRef); |
| } |
| |
| public RexNode visitLocalRef(RexLocalRef localRef) { |
| return toReplace(localRef) ? replace(localRef) : super.visitLocalRef(localRef); |
| } |
| |
| public RexNode visitLiteral(RexLiteral literal) { |
| return literal; |
| } |
| |
| public RexNode visitDynamicParam(RexDynamicParam dynamicParam) { |
| return dynamicParam; |
| } |
| |
| public RexNode visitRangeRef(RexRangeRef rangeRef) { |
| return toReplace(rangeRef) ? replace(rangeRef) : super.visitRangeRef(rangeRef); |
| } |
| } |
| |
| } |