blob: 8b717c2e913b41a0724faaa71377aff707612c7f [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.logical;
import org.apache.calcite.adapter.enumerable.EnumerableTableScan;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptTable;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.rules.ProjectRemoveRule;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexNode;
import org.apache.drill.common.exceptions.DrillRuntimeException;
import org.apache.drill.exec.planner.common.DrillProjectRelBase;
import org.apache.drill.exec.planner.common.DrillRelOptUtil;
import org.apache.drill.exec.planner.common.DrillRelOptUtil.ProjectPushInfo;
import org.apache.drill.exec.planner.common.DrillScanRelBase;
import org.apache.drill.exec.planner.physical.Prel;
import org.apache.drill.exec.planner.physical.ProjectPrel;
import org.apache.drill.exec.planner.physical.ScanPrel;
import org.apache.drill.exec.util.Utilities;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* When table support project push down, rule can be applied to reduce number of read columns
* thus improving scan operator performance.
*/
public class DrillPushProjectIntoScanRule extends RelOptRule {
public static final RelOptRule INSTANCE =
new DrillPushProjectIntoScanRule(LogicalProject.class,
EnumerableTableScan.class,
"DrillPushProjectIntoScanRule:enumerable") {
@Override
protected boolean skipScanConversion(RelDataType projectRelDataType, TableScan scan) {
// do not allow skipping conversion of EnumerableTableScan to DrillScanRel if rule is applicable
return false;
}
};
public static final RelOptRule DRILL_LOGICAL_INSTANCE =
new DrillPushProjectIntoScanRule(LogicalProject.class,
DrillScanRel.class,
"DrillPushProjectIntoScanRule:logical");
public static final RelOptRule DRILL_PHYSICAL_INSTANCE =
new DrillPushProjectIntoScanRule(ProjectPrel.class,
ScanPrel.class,
"DrillPushProjectIntoScanRule:physical") {
@Override
protected ScanPrel createScan(TableScan scan, ProjectPushInfo projectPushInfo) {
ScanPrel drillScan = (ScanPrel) scan;
return new ScanPrel(drillScan.getCluster(),
drillScan.getTraitSet().plus(Prel.DRILL_PHYSICAL),
drillScan.getGroupScan().clone(projectPushInfo.getFields()),
projectPushInfo.createNewRowType(drillScan.getCluster().getTypeFactory()),
drillScan.getTable());
}
@Override
protected ProjectPrel createProject(Project project, TableScan newScan, List<RexNode> newProjects) {
return new ProjectPrel(project.getCluster(),
project.getTraitSet().plus(Prel.DRILL_PHYSICAL),
newScan,
newProjects,
project.getRowType());
}
};
private DrillPushProjectIntoScanRule(Class<? extends Project> projectClass, Class<? extends TableScan> scanClass, String description) {
super(RelOptHelper.some(projectClass, RelOptHelper.any(scanClass)), description);
}
@Override
public void onMatch(RelOptRuleCall call) {
Project project = call.rel(0);
TableScan scan = call.rel(1);
try {
if (scan.getRowType().getFieldList().isEmpty()) {
return;
}
ProjectPushInfo projectPushInfo = DrillRelOptUtil.getFieldsInformation(scan.getRowType(), project.getProjects());
if (!canPushProjectIntoScan(scan.getTable(), projectPushInfo)
|| skipScanConversion(projectPushInfo.createNewRowType(project.getCluster().getTypeFactory()), scan)) {
// project above scan may be removed in ProjectRemoveRule for the case when it is trivial
return;
}
DrillScanRelBase newScan = createScan(scan, projectPushInfo);
List<RexNode> newProjects = new ArrayList<>();
for (RexNode n : project.getChildExps()) {
newProjects.add(n.accept(projectPushInfo.getInputReWriter()));
}
DrillProjectRelBase newProject =
createProject(project, newScan, newProjects);
if (ProjectRemoveRule.isTrivial(newProject)) {
call.transformTo(newScan);
} else {
call.transformTo(newProject);
}
} catch (IOException e) {
throw new DrillRuntimeException(e);
}
}
/**
* Checks whether conversion of input {@code TableScan} instance to target
* {@code TableScan} may be omitted.
*
* @param projectRelDataType project rel data type
* @param scan TableScan to be checked
* @return true if specified {@code TableScan} has the same row type as specified.
*/
protected boolean skipScanConversion(RelDataType projectRelDataType, TableScan scan) {
return projectRelDataType.equals(scan.getRowType());
}
/**
* Creates new {@code DrillProjectRelBase} instance with specified {@code TableScan newScan} child
* and {@code List<RexNode> newProjects} expressions using specified {@code Project project} as prototype.
*
* @param project the prototype of resulting project
* @param newScan new project child
* @param newProjects new project expressions
* @return new project instance
*/
protected DrillProjectRelBase createProject(Project project, TableScan newScan, List<RexNode> newProjects) {
return new DrillProjectRel(project.getCluster(),
project.getTraitSet().plus(DrillRel.DRILL_LOGICAL),
newScan,
newProjects,
project.getRowType());
}
/**
* Creates new {@code DrillScanRelBase} instance with row type and fields list
* obtained from specified {@code ProjectPushInfo projectPushInfo}
* using specified {@code TableScan scan} as prototype.
*
* @param scan the prototype of resulting scan
* @param projectPushInfo the source of row type and fields list
* @return new scan instance
*/
protected DrillScanRelBase createScan(TableScan scan, ProjectPushInfo projectPushInfo) {
return new DrillScanRel(scan.getCluster(),
scan.getTraitSet().plus(DrillRel.DRILL_LOGICAL),
scan.getTable(),
projectPushInfo.createNewRowType(scan.getCluster().getTypeFactory()),
projectPushInfo.getFields());
}
/**
* Push project into scan be done only if this is not a star query and
* table supports project push down.
*
* @param table table instance
* @param projectPushInfo fields information
* @return true if push project into scan can be performed, false otherwise
*/
private boolean canPushProjectIntoScan(RelOptTable table, ProjectPushInfo projectPushInfo) throws IOException {
DrillTable drillTable = Utilities.getDrillTable(table);
return !Utilities.isStarQuery(projectPushInfo.getFields())
&& drillTable.getGroupScan().canPushdownProjects(projectPushInfo.getFields());
}
}