Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CALCITE-3409] Add an interface in RelOptMaterializations… #2094

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -61,6 +61,23 @@ public abstract class RelOptMaterializations {
*/
public static List<Pair<RelNode, List<RelOptMaterialization>>> useMaterializedViews(
final RelNode rel, List<RelOptMaterialization> materializations) {
return useMaterializedViews(rel, materializations, SubstitutionVisitor.DEFAULT_RULES);
}

/**
* Returns a list of RelNode transformed from all possible combination of
* materialized view uses. Big queries will likely have more than one
* transformed RelNode, e.g., (t1 group by c1) join (t2 group by c2).
* In addition, you can add custom materialized view recognition rules.
* @param rel the original RelNode
* @param materializations the materialized view list
* @param materializationRules the materialized view recognition rules
* @return the list of transformed RelNode together with their corresponding
* materialized views used in the transformation.
*/
public static List<Pair<RelNode, List<RelOptMaterialization>>> useMaterializedViews(
final RelNode rel, List<RelOptMaterialization> materializations,
List<SubstitutionVisitor.UnifyRule> materializationRules) {
jcamachor marked this conversation as resolved.
Show resolved Hide resolved
final List<RelOptMaterialization> applicableMaterializations =
getApplicableMaterializations(rel, materializations);
xy2953396112 marked this conversation as resolved.
Show resolved Hide resolved
final List<Pair<RelNode, List<RelOptMaterialization>>> applied =
Expand All @@ -70,7 +87,7 @@ public static List<Pair<RelNode, List<RelOptMaterialization>>> useMaterializedVi
int count = applied.size();
for (int i = 0; i < count; i++) {
Pair<RelNode, List<RelOptMaterialization>> current = applied.get(i);
List<RelNode> sub = substitute(current.left, m);
List<RelNode> sub = substitute(current.left, m, materializationRules);
if (!sub.isEmpty()) {
ImmutableList.Builder<RelOptMaterialization> builder =
ImmutableList.builder();
Expand Down Expand Up @@ -170,7 +187,8 @@ && usesTable(materialization.qualifiedTableName, queryTablesUsed, frozenGraph))
}

private static List<RelNode> substitute(
RelNode root, RelOptMaterialization materialization) {
RelNode root, RelOptMaterialization materialization,
List<SubstitutionVisitor.UnifyRule> materializationRules) {
// First, if the materialization is in terms of a star table, rewrite
// the query in terms of the star table.
if (materialization.starRelOptTable != null) {
Expand Down Expand Up @@ -214,7 +232,10 @@ private static List<RelNode> substitute(
hepPlanner.setRoot(root);
root = hepPlanner.findBestExp();

return new SubstitutionVisitor(target, root).go(materialization.tableRel);
return new SubstitutionVisitor(target, root, ImmutableList.
<SubstitutionVisitor.UnifyRule>builder()
.addAll(materializationRules)
.build()).go(materialization.tableRel);
}

/**
Expand Down
Expand Up @@ -126,7 +126,7 @@
public class SubstitutionVisitor {
private static final boolean DEBUG = CalciteSystemProperty.DEBUG.value();

protected static final ImmutableList<UnifyRule> DEFAULT_RULES =
public static final ImmutableList<UnifyRule> DEFAULT_RULES =
ImmutableList.of(
TrivialRule.INSTANCE,
ScanToCalcUnifyRule.INSTANCE,
Expand Down Expand Up @@ -862,7 +862,7 @@ protected static class MatchFailed extends ControlFlowException {
* <p>The rule declares the query and target types; this allows the
* engine to fire only a few rules in a given context.</p>
*/
protected abstract static class UnifyRule {
public abstract static class UnifyRule {
protected final int slotCount;
protected final Operand queryOperand;
protected final Operand targetOperand;
Expand Down Expand Up @@ -929,7 +929,7 @@ protected <E> ImmutableList<E> copy(E[] slots, int slotCount) {
/**
* Arguments to an application of a {@link UnifyRule}.
*/
protected class UnifyRuleCall {
public class UnifyRuleCall {
protected final UnifyRule rule;
public final MutableRel query;
public final MutableRel target;
Expand Down Expand Up @@ -984,7 +984,7 @@ public RexSimplify getSimplify() {
* contains {@code target}. {@code stopTrying} indicates whether there's
* no need to do matching for the same query node again.
*/
protected static class UnifyResult {
public static class UnifyResult {
private final UnifyRuleCall call;
private final MutableRel result;
private final boolean stopTrying;
Expand All @@ -999,7 +999,7 @@ assert equalType("query", call.query, "result", result,
}

/** Abstract base class for implementing {@link UnifyRule}. */
protected abstract static class AbstractUnifyRule extends UnifyRule {
public abstract static class AbstractUnifyRule extends UnifyRule {
@SuppressWarnings("method.invocation.invalid")
protected AbstractUnifyRule(Operand queryOperand, Operand targetOperand,
int slotCount) {
Expand Down Expand Up @@ -1762,7 +1762,7 @@ private static int fieldCnt(MutableRel rel) {
}

/** Explain filtering condition and projections from MutableCalc. */
private static Pair<RexNode, List<RexNode>> explainCalc(MutableCalc calc) {
public static Pair<RexNode, List<RexNode>> explainCalc(MutableCalc calc) {
final RexShuttle shuttle = getExpandShuttle(calc.program);
final RexNode condition = shuttle.apply(calc.program.getCondition());
xy2953396112 marked this conversation as resolved.
Show resolved Hide resolved
final List<RexNode> projects = new ArrayList<>();
Expand Down Expand Up @@ -2086,7 +2086,7 @@ public static boolean equalType(String desc0, MutableRel rel0, String desc1,
}

/** Operand to a {@link UnifyRule}. */
protected abstract static class Operand {
public abstract static class Operand {
protected final Class<? extends MutableRel> clazz;

protected Operand(Class<? extends MutableRel> clazz) {
Expand Down
@@ -0,0 +1,179 @@
/*
* 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.calcite.materialize;

import org.apache.calcite.plan.RelOptMaterialization;
import org.apache.calcite.plan.RelOptMaterializations;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelTraitDef;
import org.apache.calcite.plan.SubstitutionVisitor;
import org.apache.calcite.plan.SubstitutionVisitor.UnifyRule;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.mutable.MutableCalc;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.impl.AbstractTable;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.SqlToRelTestBase;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Pair;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;

import static org.apache.calcite.test.Matchers.isLinux;

import static org.hamcrest.MatcherAssert.assertThat;

/**
* Unit tests for {@link RelOptMaterializations#useMaterializedViews}.
*/
public class CustomMaterializedViewRecognitionRuleTest extends SqlToRelTestBase {

public static Frameworks.ConfigBuilder config() {
final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
rootSchema.add("mv0", new AbstractTable() {
@Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
return typeFactory.builder()
.add("empno", SqlTypeName.INTEGER)
.add("ename", SqlTypeName.VARCHAR)
.add("job", SqlTypeName.VARCHAR)
.add("mgr", SqlTypeName.SMALLINT)
.add("hiredate", SqlTypeName.DATE)
.add("sal", SqlTypeName.DECIMAL)
.add("comm", SqlTypeName.DECIMAL)
.add("deptno", SqlTypeName.TINYINT)
.build();
}
});
return Frameworks.newConfigBuilder()
.parserConfig(SqlParser.Config.DEFAULT)
.defaultSchema(
CalciteAssert.addSchema(rootSchema, CalciteAssert.SchemaSpec.SCOTT_WITH_TEMPORAL))
.traitDefs((List<RelTraitDef>) null);
}

@Test void testCushionLikeOperatorRecognitionRule() {
final RelBuilder relBuilder = RelBuilder.create(config().build());
final RelNode query = relBuilder.scan("EMP")
.filter(
relBuilder.call(SqlStdOperatorTable.LIKE,
relBuilder.field(1), relBuilder.literal("ABCD%")))
.build();
final RelNode target = relBuilder.scan("EMP")
.filter(
relBuilder.call(SqlStdOperatorTable.LIKE,
relBuilder.field(1), relBuilder.literal("ABC%")))
.build();
final RelNode replacement = relBuilder.scan("mv0").build();
final RelOptMaterialization relOptMaterialization =
new RelOptMaterialization(replacement,
target, null, Lists.newArrayList("mv0"));
final List<UnifyRule> rules = new ArrayList<>();
rules.addAll(SubstitutionVisitor.DEFAULT_RULES);
rules.add(CustomizedMaterializationRule.INSTANCE);
final List<Pair<RelNode, List<RelOptMaterialization>>> relOptimized =
RelOptMaterializations.useMaterializedViews(query,
ImmutableList.of(relOptMaterialization), rules);
final String optimized = ""
+ "LogicalCalc(expr#0..7=[{inputs}], expr#8=['ABCD%'], expr#9=[LIKE($t1, $t8)], proj#0."
+ ".7=[{exprs}], $condition=[$t9])\n"
+ " LogicalProject(empno=[CAST($0):SMALLINT NOT NULL], ename=[CAST($1):VARCHAR(10)], "
+ "job=[CAST($2):VARCHAR(9)], mgr=[CAST($3):SMALLINT], hiredate=[CAST($4):DATE], "
+ "sal=[CAST($5):DECIMAL(7, 2)], comm=[CAST($6):DECIMAL(7, 2)], deptno=[CAST($7)"
+ ":TINYINT])\n"
+ " LogicalTableScan(table=[[mv0]])\n";
final String relOptimizedStr = RelOptUtil.toString(relOptimized.get(0).getKey());
assertThat(relOptimizedStr, isLinux(optimized));
}

/**
* A customized materialization rule, which match expression of 'LIKE'
* and match by compensation.
*/
private static class CustomizedMaterializationRule
extends SubstitutionVisitor.AbstractUnifyRule {

public static final CustomizedMaterializationRule INSTANCE =
new CustomizedMaterializationRule();

private CustomizedMaterializationRule() {
super(operand(MutableCalc.class, query(0)),
operand(MutableCalc.class, target(0)), 1);
}

@Override protected SubstitutionVisitor.UnifyResult apply(
SubstitutionVisitor.UnifyRuleCall call) {
final MutableCalc query = (MutableCalc) call.query;
final Pair<RexNode, List<RexNode>> queryExplained = SubstitutionVisitor.explainCalc(query);
final RexNode queryCond = queryExplained.left;
final List<RexNode> queryProjs = queryExplained.right;

final MutableCalc target = (MutableCalc) call.target;
final Pair<RexNode, List<RexNode>> targetExplained = SubstitutionVisitor.explainCalc(target);
final RexNode targetCond = targetExplained.left;
final List<RexNode> targetProjs = targetExplained.right;
final List parsedQ = parseLikeCondition(queryCond);
final List parsedT = parseLikeCondition(targetCond);
if (RexUtil.isIdentity(queryProjs, query.getInput().rowType)
&& RexUtil.isIdentity(targetProjs, target.getInput().rowType)
&& parsedQ != null && parsedT != null) {
if (parsedQ.get(0).equals(parsedT.get(0))) {
String literalQ = ((NlsString) parsedQ.get(1)).getValue();
String literalT = ((NlsString) parsedT.get(1)).getValue();
if (literalQ.endsWith("%") && literalT.endsWith("%")
&& !literalQ.equals(literalT)
&& literalQ.startsWith(literalT.substring(0, literalT.length() - 1))) {
return call.result(MutableCalc.of(target, query.program));
}
}
}
return null;
}

private List parseLikeCondition(RexNode rexNode) {
if (rexNode instanceof RexCall) {
RexCall rexCall = (RexCall) rexNode;
if (rexCall.getKind() == SqlKind.LIKE
&& rexCall.operands.get(0) instanceof RexInputRef
&& rexCall.operands.get(1) instanceof RexLiteral) {
return ImmutableList.of(rexCall.operands.get(0),
((RexLiteral) (rexCall.operands.get(1))).getValue());
}
}
return null;
}
}

}