blob: d6df12f8b405a55f0363c4e4b19da28946c68dab [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.generators;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.drill.exec.physical.base.IndexGroupScan;
import org.apache.drill.exec.planner.index.IndexLogicalPlanCallContext;
import org.apache.drill.exec.planner.index.IndexDescriptor;
import org.apache.drill.exec.planner.index.FunctionalIndexInfo;
import org.apache.drill.exec.planner.index.FunctionalIndexHelper;
import org.apache.drill.exec.planner.index.IndexPlanUtils;
import org.apache.drill.exec.planner.index.SimpleRexRemap;
import org.apache.drill.exec.planner.logical.DrillMergeProjectRule;
import org.apache.drill.exec.planner.logical.DrillParseContext;
import org.apache.drill.exec.planner.physical.FilterPrel;
import org.apache.drill.exec.planner.physical.PlannerSettings;
import org.apache.drill.exec.planner.physical.Prel;
import org.apache.drill.exec.planner.physical.PrelUtil;
import org.apache.drill.exec.planner.physical.ProjectPrel;
import org.apache.drill.exec.planner.physical.Prule;
import org.apache.drill.exec.planner.physical.ScanPrel;
import org.apache.calcite.rel.InvalidRelException;
import org.apache.calcite.rel.RelNode;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Generate a covering index plan that is equivalent to the original plan.
*
* This plan will be further optimized by the filter pushdown rule of the Index plugin which should
* push this filter into the index scan.
*/
public class CoveringIndexPlanGenerator extends AbstractIndexPlanGenerator {
static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(CoveringIndexPlanGenerator.class);
final protected IndexGroupScan indexGroupScan;
final protected IndexDescriptor indexDesc;
// Ideally This functionInfo should be cached along with indexDesc.
final protected FunctionalIndexInfo functionInfo;
public CoveringIndexPlanGenerator(IndexLogicalPlanCallContext indexContext,
FunctionalIndexInfo functionInfo,
IndexGroupScan indexGroupScan,
RexNode indexCondition,
RexNode remainderCondition,
RexBuilder builder,
PlannerSettings settings) {
super(indexContext, indexCondition, remainderCondition, builder, settings);
this.indexGroupScan = indexGroupScan;
this.functionInfo = functionInfo;
this.indexDesc = functionInfo.getIndexDesc();
}
/**
*
* @param inputIndex
* @param functionInfo functional index information that may impact rewrite
* @return
*/
private RexNode rewriteFunctionalCondition(RexNode inputIndex, RelDataType newRowType,
FunctionalIndexInfo functionInfo) {
if (!functionInfo.hasFunctional()) {
return inputIndex;
}
return FunctionalIndexHelper.convertConditionForIndexScan(inputIndex,
origScan, newRowType, builder, functionInfo);
}
@Override
public RelNode convertChild(final RelNode filter, final RelNode input) throws InvalidRelException {
if (indexGroupScan == null) {
logger.error("Null indexgroupScan in CoveringIndexPlanGenerator.convertChild");
return null;
}
RexNode coveringCondition;
ScanPrel indexScanPrel =
IndexPlanUtils.buildCoveringIndexScan(origScan, indexGroupScan, indexContext, indexDesc);
// If remainder condition, then combine the index and remainder conditions. This is a covering plan so we can
// pushed the entire condition into the index.
coveringCondition = IndexPlanUtils.getTotalFilter(indexCondition, remainderCondition, indexScanPrel.getCluster().getRexBuilder());
RexNode newIndexCondition =
rewriteFunctionalCondition(coveringCondition, indexScanPrel.getRowType(), functionInfo);
// build collation for filter
RelTraitSet indexFilterTraitSet = indexScanPrel.getTraitSet();
FilterPrel indexFilterPrel = new FilterPrel(indexScanPrel.getCluster(), indexFilterTraitSet,
indexScanPrel, newIndexCondition);
ProjectPrel indexProjectPrel = null;
if (origProject != null) {
RelCollation collation = IndexPlanUtils.buildCollationProject(IndexPlanUtils.getProjects(origProject), null,
origScan, functionInfo, indexContext);
indexProjectPrel = new ProjectPrel(origScan.getCluster(), indexFilterTraitSet.plus(collation),
indexFilterPrel, IndexPlanUtils.getProjects(origProject), origProject.getRowType());
}
RelNode finalRel;
if (indexProjectPrel != null) {
finalRel = indexProjectPrel;
} else {
finalRel = indexFilterPrel;
}
if (upperProject != null) {
RelCollation newCollation =
IndexPlanUtils.buildCollationProject(IndexPlanUtils.getProjects(upperProject), origProject,
origScan, functionInfo, indexContext);
ProjectPrel cap = new ProjectPrel(upperProject.getCluster(),
newCollation==null?finalRel.getTraitSet() : finalRel.getTraitSet().plus(newCollation),
finalRel, IndexPlanUtils.getProjects(upperProject), upperProject.getRowType());
if (functionInfo.hasFunctional()) {
//if there is functional index field, then a rewrite may be needed in upperProject/indexProject
//merge upperProject with indexProjectPrel(from origProject) if both exist,
ProjectPrel newProject = cap;
if (indexProjectPrel != null) {
newProject = (ProjectPrel) DrillMergeProjectRule.replace(newProject, indexProjectPrel);
}
// then rewrite functional expressions in new project.
List<RexNode> newProjects = Lists.newArrayList();
DrillParseContext parseContxt = new DrillParseContext(PrelUtil.getPlannerSettings(newProject.getCluster()));
for(RexNode projectRex: newProject.getProjects()) {
RexNode newRex = IndexPlanUtils.rewriteFunctionalRex(indexContext, parseContxt, null, origScan, projectRex, indexScanPrel.getRowType(), functionInfo);
newProjects.add(newRex);
}
ProjectPrel rewrittenProject = new ProjectPrel(newProject.getCluster(),
newCollation==null? newProject.getTraitSet() : newProject.getTraitSet().plus(newCollation),
indexFilterPrel, newProjects, newProject.getRowType());
cap = rewrittenProject;
}
finalRel = cap;
}
if (indexContext.getSort() != null) {
finalRel = getSortNode(indexContext, finalRel, false,true, true);
Preconditions.checkArgument(finalRel != null);
}
finalRel = Prule.convert(finalRel, finalRel.getTraitSet().plus(Prel.DRILL_PHYSICAL));
logger.debug("CoveringIndexPlanGenerator got finalRel {} from origScan {}, original digest {}, new digest {}.",
finalRel.toString(), origScan.toString(),
upperProject==null?indexContext.getFilter().getDigest(): upperProject.getDigest(), finalRel.getDigest());
return finalRel;
}
private RexNode rewriteConditionForProject(RexNode condition, List<RexNode> projects) {
Map<RexNode, RexNode> mapping = new HashMap<>();
rewriteConditionForProjectInternal(condition, projects, mapping);
SimpleRexRemap.RexReplace replacer = new SimpleRexRemap.RexReplace(mapping);
return condition.accept(replacer);
}
private void rewriteConditionForProjectInternal(RexNode condition, List<RexNode> projects, Map<RexNode, RexNode> mapping) {
if (condition instanceof RexCall) {
if ("ITEM".equals(((RexCall) condition).getOperator().getName().toUpperCase())) {
int index = 0;
for (RexNode project : projects) {
if (project.toString().equals(condition.toString())) {
// Map it to the corresponding RexInputRef for the project
mapping.put(condition, new RexInputRef(index, project.getType()));
}
++index;
}
} else {
for (RexNode child : ((RexCall) condition).getOperands()) {
rewriteConditionForProjectInternal(child, projects, mapping);
}
}
}
}
}