com.hazelcast.jet.sql.impl.opt.logical.FilterIntoScanLogicalRule Maven / Gradle / Ivy
/*
* Copyright 2021 Hazelcast Inc.
*
* Licensed under the Hazelcast Community License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://hazelcast.com/hazelcast-community-license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.jet.sql.impl.opt.logical;
import com.hazelcast.jet.sql.impl.opt.OptUtils;
import com.hazelcast.jet.sql.impl.schema.HazelcastTable;
import com.hazelcast.sql.impl.schema.map.PartitionedMapTable;
import com.hazelcast.org.apache.calcite.plan.RelOptRule;
import com.hazelcast.org.apache.calcite.plan.RelOptRuleCall;
import com.hazelcast.org.apache.calcite.rel.core.Filter;
import com.hazelcast.org.apache.calcite.rel.core.RelFactories;
import com.hazelcast.org.apache.calcite.rel.core.TableScan;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalFilter;
import com.hazelcast.org.apache.calcite.rel.logical.LogicalTableScan;
import com.hazelcast.org.apache.calcite.rex.RexNode;
import com.hazelcast.org.apache.calcite.rex.RexUtil;
import com.hazelcast.org.apache.calcite.util.mapping.Mapping;
import com.hazelcast.org.apache.calcite.util.mapping.Mappings;
import java.util.ArrayList;
import java.util.List;
/**
* Logical rule that pushes down a {@link Filter} into a {@link TableScan} to allow for constrained scans.
* See {@link HazelcastTable} for more information about constrained scans.
*
* Before:
*
* LogicalFilter[filter=exp1]
* LogicalScan[table[filter=exp2]]
*
* After:
*
* LogicalScan[table[filter=exp1 AND exp2]]
*
*/
public final class FilterIntoScanLogicalRule extends RelOptRule {
public static final FilterIntoScanLogicalRule INSTANCE = new FilterIntoScanLogicalRule();
private FilterIntoScanLogicalRule() {
super(
operand(LogicalFilter.class,
operandJ(LogicalTableScan.class,
null,
scan -> OptUtils.hasTableType(scan, PartitionedMapTable.class),
none()
)
),
RelFactories.LOGICAL_BUILDER,
FilterIntoScanLogicalRule.class.getSimpleName()
);
}
@Override
public void onMatch(RelOptRuleCall call) {
Filter filter = call.rel(0);
TableScan scan = call.rel(1);
HazelcastTable originalTable = OptUtils.extractHazelcastTable(scan);
// Remap the condition to the original TableScan columns.
RexNode newCondition = remapCondition(originalTable, filter.getCondition());
// Compose the conjunction with the old filter if needed.
RexNode originalCondition = originalTable.getFilter();
if (originalCondition != null) {
List nodes = new ArrayList<>(2);
nodes.add(originalCondition);
nodes.add(newCondition);
newCondition = RexUtil.composeConjunction(scan.getCluster().getRexBuilder(), nodes, true);
}
// Create a scan with a new filter.
LogicalTableScan newScan = OptUtils.createLogicalScan(
scan,
originalTable.withFilter(newCondition)
);
call.transformTo(newScan);
}
/**
* Remaps the column indexes referenced in the {@code Filter} to match the original indexed used by {@code TableScan}.
*
* Consider the following query: "SELECT f1, f0 FROM t WHERE f0 > ?" for the table {@code t[f0, f1]}
*
* The original tree before optimization:
*
* LogicalFilter[$1>?] // f0 is referenced as $1
* LogicalProject[$1, $0] // f1, f0
* LogicalScan[table=t[projects=[0, 1]]] // f0, f1
*
* After project pushdown:
*
* LogicalFilter[$1>?] // f0 is referenced as $1
* LogicalScan[table=t[projects=[1, 0]]] // f1, f0
*
* After filter pushdown:
*
* LogicalScan[table=t[projects=[1, 0], filter=[$0>?]]] // f0 is referenced as $0
*
*
* @param originalHazelcastTable The original table from the {@code TableScan} before the pushdown
* @param originalFilterCondition The original condition from the {@code Filter}.
* @return New condition that is going to be pushed down to a {@code TableScan}.
*/
private static RexNode remapCondition(HazelcastTable originalHazelcastTable, RexNode originalFilterCondition) {
List projects = originalHazelcastTable.getProjects();
Mapping mapping = Mappings.source(projects, originalHazelcastTable.getOriginalFieldCount());
return RexUtil.apply(mapping, originalFilterCondition);
}
}