Search in sources :

Example 21 with RelMetadataQuery

use of org.apache.calcite.rel.metadata.RelMetadataQuery in project calcite by apache.

the class AbstractMaterializedViewRule method perform.

/**
 * Rewriting logic is based on "Optimizing Queries Using Materialized Views:
 * A Practical, Scalable Solution" by Goldstein and Larson.
 *
 * <p>On the query side, rules matches a Project-node chain or node, where node
 * is either an Aggregate or a Join. Subplan rooted at the node operator must
 * be composed of one or more of the following operators: TableScan, Project,
 * Filter, and Join.
 *
 * <p>For each join MV, we need to check the following:
 * <ol>
 * <li> The plan rooted at the Join operator in the view produces all rows
 * needed by the plan rooted at the Join operator in the query.</li>
 * <li> All columns required by compensating predicates, i.e., predicates that
 * need to be enforced over the view, are available at the view output.</li>
 * <li> All output expressions can be computed from the output of the view.</li>
 * <li> All output rows occur with the correct duplication factor. We might
 * rely on existing Unique-Key - Foreign-Key relationships to extract that
 * information.</li>
 * </ol>
 *
 * <p>In turn, for each aggregate MV, we need to check the following:
 * <ol>
 * <li> The plan rooted at the Aggregate operator in the view produces all rows
 * needed by the plan rooted at the Aggregate operator in the query.</li>
 * <li> All columns required by compensating predicates, i.e., predicates that
 * need to be enforced over the view, are available at the view output.</li>
 * <li> The grouping columns in the query are a subset of the grouping columns
 * in the view.</li>
 * <li> All columns required to perform further grouping are available in the
 * view output.</li>
 * <li> All columns required to compute output expressions are available in the
 * view output.</li>
 * </ol>
 *
 * <p>The rule contains multiple extensions compared to the original paper. One of
 * them is the possibility of creating rewritings using Union operators, e.g., if
 * the result of a query is partially contained in the materialized view.
 */
protected void perform(RelOptRuleCall call, Project topProject, RelNode node) {
    final RexBuilder rexBuilder = node.getCluster().getRexBuilder();
    final RelMetadataQuery mq = RelMetadataQuery.instance();
    final RelOptPlanner planner = call.getPlanner();
    final RexExecutor executor = Util.first(planner.getExecutor(), RexUtil.EXECUTOR);
    final RelOptPredicateList predicates = RelOptPredicateList.EMPTY;
    final RexSimplify simplify = new RexSimplify(rexBuilder, predicates, true, executor);
    final List<RelOptMaterialization> materializations = (planner instanceof VolcanoPlanner) ? ((VolcanoPlanner) planner).getMaterializations() : ImmutableList.<RelOptMaterialization>of();
    if (!materializations.isEmpty()) {
        // try to generate a rewriting are met
        if (!isValidPlan(topProject, node, mq)) {
            return;
        }
        // Obtain applicable (filtered) materializations
        // TODO: Filtering of relevant materializations needs to be
        // improved so we gather only materializations that might
        // actually generate a valid rewriting.
        final List<RelOptMaterialization> applicableMaterializations = RelOptMaterializations.getApplicableMaterializations(node, materializations);
        if (!applicableMaterializations.isEmpty()) {
            // 2. Initialize all query related auxiliary data structures
            // that will be used throughout query rewriting process
            // Generate query table references
            final Set<RelTableRef> queryTableRefs = mq.getTableReferences(node);
            if (queryTableRefs == null) {
                // Bail out
                return;
            }
            // Extract query predicates
            final RelOptPredicateList queryPredicateList = mq.getAllPredicates(node);
            if (queryPredicateList == null) {
                // Bail out
                return;
            }
            final RexNode pred = simplify.simplify(RexUtil.composeConjunction(rexBuilder, queryPredicateList.pulledUpPredicates, false));
            final Triple<RexNode, RexNode, RexNode> queryPreds = splitPredicates(rexBuilder, pred);
            // Extract query equivalence classes. An equivalence class is a set
            // of columns in the query output that are known to be equal.
            final EquivalenceClasses qEC = new EquivalenceClasses();
            for (RexNode conj : RelOptUtil.conjunctions(queryPreds.getLeft())) {
                assert conj.isA(SqlKind.EQUALS);
                RexCall equiCond = (RexCall) conj;
                qEC.addEquivalenceClass((RexTableInputRef) equiCond.getOperands().get(0), (RexTableInputRef) equiCond.getOperands().get(1));
            }
            // rewrite the given query
            for (RelOptMaterialization materialization : applicableMaterializations) {
                RelNode view = materialization.tableRel;
                Project topViewProject;
                RelNode viewNode;
                if (materialization.queryRel instanceof Project) {
                    topViewProject = (Project) materialization.queryRel;
                    viewNode = topViewProject.getInput();
                } else {
                    topViewProject = null;
                    viewNode = materialization.queryRel;
                }
                // 3.1. View checks before proceeding
                if (!isValidPlan(topViewProject, viewNode, mq)) {
                    // Skip it
                    continue;
                }
                // 3.2. Initialize all query related auxiliary data structures
                // that will be used throughout query rewriting process
                // Extract view predicates
                final RelOptPredicateList viewPredicateList = mq.getAllPredicates(viewNode);
                if (viewPredicateList == null) {
                    // Skip it
                    continue;
                }
                final RexNode viewPred = simplify.simplify(RexUtil.composeConjunction(rexBuilder, viewPredicateList.pulledUpPredicates, false));
                final Triple<RexNode, RexNode, RexNode> viewPreds = splitPredicates(rexBuilder, viewPred);
                // Extract view table references
                final Set<RelTableRef> viewTableRefs = mq.getTableReferences(viewNode);
                if (viewTableRefs == null) {
                    // Bail out
                    return;
                }
                // Extract view tables
                MatchModality matchModality;
                Multimap<RexTableInputRef, RexTableInputRef> compensationEquiColumns = ArrayListMultimap.create();
                if (!queryTableRefs.equals(viewTableRefs)) {
                    // subset of query tables (add additional tables through joins if possible)
                    if (viewTableRefs.containsAll(queryTableRefs)) {
                        matchModality = MatchModality.QUERY_PARTIAL;
                        final EquivalenceClasses vEC = new EquivalenceClasses();
                        for (RexNode conj : RelOptUtil.conjunctions(viewPreds.getLeft())) {
                            assert conj.isA(SqlKind.EQUALS);
                            RexCall equiCond = (RexCall) conj;
                            vEC.addEquivalenceClass((RexTableInputRef) equiCond.getOperands().get(0), (RexTableInputRef) equiCond.getOperands().get(1));
                        }
                        if (!compensatePartial(viewTableRefs, vEC, queryTableRefs, compensationEquiColumns)) {
                            // Cannot rewrite, skip it
                            continue;
                        }
                    } else if (queryTableRefs.containsAll(viewTableRefs)) {
                        matchModality = MatchModality.VIEW_PARTIAL;
                        ViewPartialRewriting partialRewritingResult = compensateViewPartial(call.builder(), rexBuilder, mq, view, topProject, node, queryTableRefs, qEC, topViewProject, viewNode, viewTableRefs);
                        if (partialRewritingResult == null) {
                            // Cannot rewrite, skip it
                            continue;
                        }
                        // Rewrite succeeded
                        view = partialRewritingResult.newView;
                        topViewProject = partialRewritingResult.newTopViewProject;
                        viewNode = partialRewritingResult.newViewNode;
                    } else {
                        // Skip it
                        continue;
                    }
                } else {
                    matchModality = MatchModality.COMPLETE;
                }
                // 4. We map every table in the query to a table with the same qualified
                // name (all query tables are contained in the view, thus this is equivalent
                // to mapping every table in the query to a view table).
                final Multimap<RelTableRef, RelTableRef> multiMapTables = ArrayListMultimap.create();
                for (RelTableRef queryTableRef1 : queryTableRefs) {
                    for (RelTableRef queryTableRef2 : queryTableRefs) {
                        if (queryTableRef1.getQualifiedName().equals(queryTableRef2.getQualifiedName())) {
                            multiMapTables.put(queryTableRef1, queryTableRef2);
                        }
                    }
                }
                // If a table is used multiple times, we will create multiple mappings,
                // and we will try to rewrite the query using each of the mappings.
                // Then, we will try to map every source table (query) to a target
                // table (view), and if we are successful, we will try to create
                // compensation predicates to filter the view results further
                // (if needed).
                final List<BiMap<RelTableRef, RelTableRef>> flatListMappings = generateTableMappings(multiMapTables);
                for (BiMap<RelTableRef, RelTableRef> queryToViewTableMapping : flatListMappings) {
                    // TableMapping : mapping query tables -> view tables
                    // 4.0. If compensation equivalence classes exist, we need to add
                    // the mapping to the query mapping
                    final EquivalenceClasses currQEC = EquivalenceClasses.copy(qEC);
                    if (matchModality == MatchModality.QUERY_PARTIAL) {
                        for (Entry<RexTableInputRef, RexTableInputRef> e : compensationEquiColumns.entries()) {
                            // Copy origin
                            RelTableRef queryTableRef = queryToViewTableMapping.inverse().get(e.getKey().getTableRef());
                            RexTableInputRef queryColumnRef = RexTableInputRef.of(queryTableRef, e.getKey().getIndex(), e.getKey().getType());
                            // Add to query equivalence classes and table mapping
                            currQEC.addEquivalenceClass(queryColumnRef, e.getValue());
                            queryToViewTableMapping.put(e.getValue().getTableRef(), // identity
                            e.getValue().getTableRef());
                        }
                    }
                    // 4.1. Compute compensation predicates, i.e., predicates that need to be
                    // enforced over the view to retain query semantics. The resulting predicates
                    // are expressed using {@link RexTableInputRef} over the query.
                    // First, to establish relationship, we swap column references of the view
                    // predicates to point to query tables and compute equivalence classes.
                    final RexNode viewColumnsEquiPred = RexUtil.swapTableReferences(rexBuilder, viewPreds.getLeft(), queryToViewTableMapping.inverse());
                    final EquivalenceClasses queryBasedVEC = new EquivalenceClasses();
                    for (RexNode conj : RelOptUtil.conjunctions(viewColumnsEquiPred)) {
                        assert conj.isA(SqlKind.EQUALS);
                        RexCall equiCond = (RexCall) conj;
                        queryBasedVEC.addEquivalenceClass((RexTableInputRef) equiCond.getOperands().get(0), (RexTableInputRef) equiCond.getOperands().get(1));
                    }
                    Triple<RexNode, RexNode, RexNode> compensationPreds = computeCompensationPredicates(rexBuilder, simplify, currQEC, queryPreds, queryBasedVEC, viewPreds, queryToViewTableMapping);
                    if (compensationPreds == null && generateUnionRewriting) {
                        // Attempt partial rewriting using union operator. This rewriting
                        // will read some data from the view and the rest of the data from
                        // the query computation. The resulting predicates are expressed
                        // using {@link RexTableInputRef} over the view.
                        compensationPreds = computeCompensationPredicates(rexBuilder, simplify, queryBasedVEC, viewPreds, currQEC, queryPreds, queryToViewTableMapping.inverse());
                        if (compensationPreds == null) {
                            // This was our last chance to use the view, skip it
                            continue;
                        }
                        RexNode compensationColumnsEquiPred = compensationPreds.getLeft();
                        RexNode otherCompensationPred = RexUtil.composeConjunction(rexBuilder, ImmutableList.of(compensationPreds.getMiddle(), compensationPreds.getRight()), false);
                        assert !compensationColumnsEquiPred.isAlwaysTrue() || !otherCompensationPred.isAlwaysTrue();
                        // b. Generate union branch (query).
                        final RelNode unionInputQuery = rewriteQuery(call.builder(), rexBuilder, simplify, mq, compensationColumnsEquiPred, otherCompensationPred, topProject, node, queryToViewTableMapping, queryBasedVEC, currQEC);
                        if (unionInputQuery == null) {
                            // Skip it
                            continue;
                        }
                        // c. Generate union branch (view).
                        // We trigger the unifying method. This method will either create a Project
                        // or an Aggregate operator on top of the view. It will also compute the
                        // output expressions for the query.
                        final RelNode unionInputView = rewriteView(call.builder(), rexBuilder, simplify, mq, matchModality, true, view, topProject, node, topViewProject, viewNode, queryToViewTableMapping, currQEC);
                        if (unionInputView == null) {
                            // Skip it
                            continue;
                        }
                        // d. Generate final rewriting (union).
                        final RelNode result = createUnion(call.builder(), rexBuilder, topProject, unionInputQuery, unionInputView);
                        if (result == null) {
                            // Skip it
                            continue;
                        }
                        call.transformTo(result);
                    } else if (compensationPreds != null) {
                        RexNode compensationColumnsEquiPred = compensationPreds.getLeft();
                        RexNode otherCompensationPred = RexUtil.composeConjunction(rexBuilder, ImmutableList.of(compensationPreds.getMiddle(), compensationPreds.getRight()), false);
                        // a. Compute final compensation predicate.
                        if (!compensationColumnsEquiPred.isAlwaysTrue() || !otherCompensationPred.isAlwaysTrue()) {
                            // All columns required by compensating predicates must be contained
                            // in the view output (condition 2).
                            List<RexNode> viewExprs = topViewProject == null ? extractReferences(rexBuilder, view) : topViewProject.getChildExps();
                            // since we want to enforce the rest
                            if (!compensationColumnsEquiPred.isAlwaysTrue()) {
                                compensationColumnsEquiPred = rewriteExpression(rexBuilder, mq, view, viewNode, viewExprs, queryToViewTableMapping.inverse(), queryBasedVEC, false, compensationColumnsEquiPred);
                                if (compensationColumnsEquiPred == null) {
                                    // Skip it
                                    continue;
                                }
                            }
                            // For the rest, we use the query equivalence classes
                            if (!otherCompensationPred.isAlwaysTrue()) {
                                otherCompensationPred = rewriteExpression(rexBuilder, mq, view, viewNode, viewExprs, queryToViewTableMapping.inverse(), currQEC, true, otherCompensationPred);
                                if (otherCompensationPred == null) {
                                    // Skip it
                                    continue;
                                }
                            }
                        }
                        final RexNode viewCompensationPred = RexUtil.composeConjunction(rexBuilder, ImmutableList.of(compensationColumnsEquiPred, otherCompensationPred), false);
                        // b. Generate final rewriting if possible.
                        // First, we add the compensation predicate (if any) on top of the view.
                        // Then, we trigger the unifying method. This method will either create a
                        // Project or an Aggregate operator on top of the view. It will also compute
                        // the output expressions for the query.
                        RelBuilder builder = call.builder();
                        RelNode viewWithFilter;
                        if (!viewCompensationPred.isAlwaysTrue()) {
                            RexNode newPred = simplify.simplify(viewCompensationPred);
                            viewWithFilter = builder.push(view).filter(newPred).build();
                            // We add (and push) the filter to the view plan before triggering the rewriting.
                            // This is useful in case some of the columns can be folded to same value after
                            // filter is added.
                            Pair<RelNode, RelNode> pushedNodes = pushFilterToOriginalViewPlan(builder, topViewProject, viewNode, newPred);
                            topViewProject = (Project) pushedNodes.left;
                            viewNode = pushedNodes.right;
                        } else {
                            viewWithFilter = builder.push(view).build();
                        }
                        final RelNode result = rewriteView(builder, rexBuilder, simplify, mq, matchModality, false, viewWithFilter, topProject, node, topViewProject, viewNode, queryToViewTableMapping, currQEC);
                        if (result == null) {
                            // Skip it
                            continue;
                        }
                        call.transformTo(result);
                    }
                // end else
                }
            }
        }
    }
}
Also used : RelMetadataQuery(org.apache.calcite.rel.metadata.RelMetadataQuery) RelOptPlanner(org.apache.calcite.plan.RelOptPlanner) RexCall(org.apache.calcite.rex.RexCall) RexExecutor(org.apache.calcite.rex.RexExecutor) RelOptPredicateList(org.apache.calcite.plan.RelOptPredicateList) RexBuilder(org.apache.calcite.rex.RexBuilder) RelOptPredicateList(org.apache.calcite.plan.RelOptPredicateList) List(java.util.List) ArrayList(java.util.ArrayList) ImmutableList(com.google.common.collect.ImmutableList) Pair(org.apache.calcite.util.Pair) RelBuilder(org.apache.calcite.tools.RelBuilder) BiMap(com.google.common.collect.BiMap) HashBiMap(com.google.common.collect.HashBiMap) RelTableRef(org.apache.calcite.rex.RexTableInputRef.RelTableRef) RexTableInputRef(org.apache.calcite.rex.RexTableInputRef) Project(org.apache.calcite.rel.core.Project) RelNode(org.apache.calcite.rel.RelNode) RexSimplify(org.apache.calcite.rex.RexSimplify) RelOptMaterialization(org.apache.calcite.plan.RelOptMaterialization) VolcanoPlanner(org.apache.calcite.plan.volcano.VolcanoPlanner) RexNode(org.apache.calcite.rex.RexNode)

Example 22 with RelMetadataQuery

use of org.apache.calcite.rel.metadata.RelMetadataQuery in project calcite by apache.

the class HepRelMetadataProvider method apply.

public <M extends Metadata> UnboundMetadata<M> apply(Class<? extends RelNode> relClass, final Class<? extends M> metadataClass) {
    return new UnboundMetadata<M>() {

        public M bind(RelNode rel, RelMetadataQuery mq) {
            if (!(rel instanceof HepRelVertex)) {
                return null;
            }
            HepRelVertex vertex = (HepRelVertex) rel;
            final RelNode rel2 = vertex.getCurrentRel();
            UnboundMetadata<M> function = rel.getCluster().getMetadataProvider().apply(rel2.getClass(), metadataClass);
            return function.bind(rel2, mq);
        }
    };
}
Also used : RelMetadataQuery(org.apache.calcite.rel.metadata.RelMetadataQuery) RelNode(org.apache.calcite.rel.RelNode) UnboundMetadata(org.apache.calcite.rel.metadata.UnboundMetadata)

Example 23 with RelMetadataQuery

use of org.apache.calcite.rel.metadata.RelMetadataQuery in project calcite by apache.

the class RelSubset method computeBestCost.

// ~ Methods ----------------------------------------------------------------
/**
 * Computes the best {@link RelNode} in this subset.
 *
 * <p>Only necessary when a subset is created in a set that has subsets that
 * subsume it. Rationale:</p>
 *
 * <ol>
 * <li>If the are no subsuming subsets, the subset is initially empty.</li>
 * <li>After creation, {@code best} and {@code bestCost} are maintained
 *    incrementally by {@link #propagateCostImprovements0} and
 *    {@link RelSet#mergeWith(VolcanoPlanner, RelSet)}.</li>
 * </ol>
 */
private void computeBestCost(RelOptPlanner planner) {
    bestCost = planner.getCostFactory().makeInfiniteCost();
    final RelMetadataQuery mq = getCluster().getMetadataQuery();
    for (RelNode rel : getRels()) {
        final RelOptCost cost = planner.getCost(rel, mq);
        if (cost.isLt(bestCost)) {
            bestCost = cost;
            best = rel;
        }
    }
}
Also used : RelMetadataQuery(org.apache.calcite.rel.metadata.RelMetadataQuery) AbstractRelNode(org.apache.calcite.rel.AbstractRelNode) RelNode(org.apache.calcite.rel.RelNode) RelOptCost(org.apache.calcite.plan.RelOptCost)

Example 24 with RelMetadataQuery

use of org.apache.calcite.rel.metadata.RelMetadataQuery in project calcite by apache.

the class RelMetadataTest method testDistinctRowCountTable.

@Test
public void testDistinctRowCountTable() {
    // no unique key information is available so return null
    RelNode rel = convertSql("select * from emp where deptno = 10");
    final RelMetadataQuery mq = RelMetadataQuery.instance();
    ImmutableBitSet groupKey = ImmutableBitSet.of(rel.getRowType().getFieldNames().indexOf("DEPTNO"));
    Double result = mq.getDistinctRowCount(rel, groupKey, null);
    assertThat(result, nullValue());
}
Also used : RelMetadataQuery(org.apache.calcite.rel.metadata.RelMetadataQuery) RelNode(org.apache.calcite.rel.RelNode) ImmutableBitSet(org.apache.calcite.util.ImmutableBitSet) Test(org.junit.Test)

Example 25 with RelMetadataQuery

use of org.apache.calcite.rel.metadata.RelMetadataQuery in project calcite by apache.

the class RelMetadataTest method testSelectivityAggCached.

/**
 * Checks that we can cache a metadata request that includes a null
 * argument.
 */
@Test
public void testSelectivityAggCached() {
    RelNode rel = convertSql("select deptno, count(*) from emp where deptno > 10 " + "group by deptno having count(*) = 0");
    rel.getCluster().setMetadataProvider(new CachingRelMetadataProvider(rel.getCluster().getMetadataProvider(), rel.getCluster().getPlanner()));
    final RelMetadataQuery mq = RelMetadataQuery.instance();
    Double result = mq.getSelectivity(rel, null);
    assertThat(result, within(DEFAULT_COMP_SELECTIVITY * DEFAULT_EQUAL_SELECTIVITY, EPSILON));
}
Also used : RelMetadataQuery(org.apache.calcite.rel.metadata.RelMetadataQuery) RelNode(org.apache.calcite.rel.RelNode) CachingRelMetadataProvider(org.apache.calcite.rel.metadata.CachingRelMetadataProvider) Test(org.junit.Test)

Aggregations

RelMetadataQuery (org.apache.calcite.rel.metadata.RelMetadataQuery)231 RelNode (org.apache.calcite.rel.RelNode)154 RexNode (org.apache.calcite.rex.RexNode)60 Test (org.junit.jupiter.api.Test)55 ArrayList (java.util.ArrayList)42 ImmutableBitSet (org.apache.calcite.util.ImmutableBitSet)42 RelOptCluster (org.apache.calcite.plan.RelOptCluster)39 RexBuilder (org.apache.calcite.rex.RexBuilder)36 RelOptPredicateList (org.apache.calcite.plan.RelOptPredicateList)32 RelBuilder (org.apache.calcite.tools.RelBuilder)29 RelTraitSet (org.apache.calcite.plan.RelTraitSet)26 List (java.util.List)22 Aggregate (org.apache.calcite.rel.core.Aggregate)20 HashMap (java.util.HashMap)19 RexTableInputRef (org.apache.calcite.rex.RexTableInputRef)19 AggregateCall (org.apache.calcite.rel.core.AggregateCall)18 RexInputRef (org.apache.calcite.rex.RexInputRef)18 ImmutableList (com.google.common.collect.ImmutableList)17 RelDataType (org.apache.calcite.rel.type.RelDataType)17 Join (org.apache.calcite.rel.core.Join)16