Search in sources :

Example 1 with RelTableRef

use of org.apache.calcite.rex.RexTableInputRef.RelTableRef in project calcite by apache.

the class RelMdExpressionLineage method getExpressionLineage.

/**
 * Expression lineage from {@link Join}.
 *
 * <p>We only extract the lineage for INNER joins.
 */
public Set<RexNode> getExpressionLineage(Join rel, RelMetadataQuery mq, RexNode outputExpression) {
    if (rel.getJoinType() != JoinRelType.INNER) {
        // We cannot map origin of this expression.
        return null;
    }
    final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
    final RelNode leftInput = rel.getLeft();
    final RelNode rightInput = rel.getRight();
    final int nLeftColumns = leftInput.getRowType().getFieldList().size();
    // Infer column origin expressions for given references
    final Multimap<List<String>, RelTableRef> qualifiedNamesToRefs = HashMultimap.create();
    final Map<RelTableRef, RelTableRef> currentTablesMapping = new HashMap<>();
    final Map<RexInputRef, Set<RexNode>> mapping = new LinkedHashMap<>();
    for (int idx = 0; idx < rel.getRowType().getFieldList().size(); idx++) {
        if (idx < nLeftColumns) {
            final RexInputRef inputRef = RexInputRef.of(idx, leftInput.getRowType().getFieldList());
            final Set<RexNode> originalExprs = mq.getExpressionLineage(leftInput, inputRef);
            if (originalExprs == null) {
                // Bail out
                return null;
            }
            // Gather table references, left input references remain unchanged
            final Set<RelTableRef> tableRefs = RexUtil.gatherTableReferences(Lists.newArrayList(originalExprs));
            for (RelTableRef leftRef : tableRefs) {
                qualifiedNamesToRefs.put(leftRef.getQualifiedName(), leftRef);
            }
            mapping.put(RexInputRef.of(idx, rel.getRowType().getFieldList()), originalExprs);
        } else {
            // Right input.
            final RexInputRef inputRef = RexInputRef.of(idx - nLeftColumns, rightInput.getRowType().getFieldList());
            final Set<RexNode> originalExprs = mq.getExpressionLineage(rightInput, inputRef);
            if (originalExprs == null) {
                // Bail out
                return null;
            }
            // Gather table references, right input references might need to be
            // updated if there are table names clashes with left input
            final Set<RelTableRef> tableRefs = RexUtil.gatherTableReferences(Lists.newArrayList(originalExprs));
            for (RelTableRef rightRef : tableRefs) {
                int shift = 0;
                Collection<RelTableRef> lRefs = qualifiedNamesToRefs.get(rightRef.getQualifiedName());
                if (lRefs != null) {
                    shift = lRefs.size();
                }
                currentTablesMapping.put(rightRef, RelTableRef.of(rightRef.getTable(), shift + rightRef.getEntityNumber()));
            }
            final Set<RexNode> updatedExprs = Sets.newHashSet(Iterables.transform(originalExprs, new Function<RexNode, RexNode>() {

                @Override
                public RexNode apply(RexNode e) {
                    return RexUtil.swapTableReferences(rexBuilder, e, currentTablesMapping);
                }
            }));
            mapping.put(RexInputRef.of(idx, rel.getRowType().getFieldList()), updatedExprs);
        }
    }
    // Return result
    return createAllPossibleExpressions(rexBuilder, outputExpression, mapping);
}
Also used : HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet) ImmutableBitSet(org.apache.calcite.util.ImmutableBitSet) Set(java.util.Set) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) RelTableRef(org.apache.calcite.rex.RexTableInputRef.RelTableRef) LinkedHashMap(java.util.LinkedHashMap) Function(com.google.common.base.Function) RelNode(org.apache.calcite.rel.RelNode) RexBuilder(org.apache.calcite.rex.RexBuilder) RexInputRef(org.apache.calcite.rex.RexInputRef) ArrayList(java.util.ArrayList) List(java.util.List) RexNode(org.apache.calcite.rex.RexNode)

Example 2 with RelTableRef

use of org.apache.calcite.rex.RexTableInputRef.RelTableRef in project calcite by apache.

the class RelMdExpressionLineage method getExpressionLineage.

/**
 * Expression lineage from {@link Union}.
 *
 * <p>For Union operator, we might be able to extract multiple origins for the
 * references in the given expression.
 */
public Set<RexNode> getExpressionLineage(Union rel, RelMetadataQuery mq, RexNode outputExpression) {
    final RexBuilder rexBuilder = rel.getCluster().getRexBuilder();
    // Infer column origin expressions for given references
    final Multimap<List<String>, RelTableRef> qualifiedNamesToRefs = HashMultimap.create();
    final Map<RexInputRef, Set<RexNode>> mapping = new LinkedHashMap<>();
    for (RelNode input : rel.getInputs()) {
        final Map<RelTableRef, RelTableRef> currentTablesMapping = new HashMap<>();
        for (int idx = 0; idx < input.getRowType().getFieldList().size(); idx++) {
            final RexInputRef inputRef = RexInputRef.of(idx, input.getRowType().getFieldList());
            final Set<RexNode> originalExprs = mq.getExpressionLineage(input, inputRef);
            if (originalExprs == null) {
                // Bail out
                return null;
            }
            final RexInputRef ref = RexInputRef.of(idx, rel.getRowType().getFieldList());
            // Gather table references, references might need to be
            // updated
            final Set<RelTableRef> tableRefs = RexUtil.gatherTableReferences(Lists.newArrayList(originalExprs));
            for (RelTableRef tableRef : tableRefs) {
                int shift = 0;
                Collection<RelTableRef> lRefs = qualifiedNamesToRefs.get(tableRef.getQualifiedName());
                if (lRefs != null) {
                    shift = lRefs.size();
                }
                currentTablesMapping.put(tableRef, RelTableRef.of(tableRef.getTable(), shift + tableRef.getEntityNumber()));
            }
            final Set<RexNode> updatedExprs = Sets.newHashSet(Iterables.transform(originalExprs, new Function<RexNode, RexNode>() {

                @Override
                public RexNode apply(RexNode e) {
                    return RexUtil.swapTableReferences(rexBuilder, e, currentTablesMapping);
                }
            }));
            final Set<RexNode> set = mapping.get(ref);
            if (set != null) {
                set.addAll(updatedExprs);
            } else {
                mapping.put(ref, updatedExprs);
            }
        }
        // Add to existing qualified names
        for (RelTableRef newRef : currentTablesMapping.values()) {
            qualifiedNamesToRefs.put(newRef.getQualifiedName(), newRef);
        }
    }
    // Return result
    return createAllPossibleExpressions(rexBuilder, outputExpression, mapping);
}
Also used : HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet) ImmutableBitSet(org.apache.calcite.util.ImmutableBitSet) Set(java.util.Set) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) RelTableRef(org.apache.calcite.rex.RexTableInputRef.RelTableRef) LinkedHashMap(java.util.LinkedHashMap) Function(com.google.common.base.Function) RelNode(org.apache.calcite.rel.RelNode) RexBuilder(org.apache.calcite.rex.RexBuilder) RexInputRef(org.apache.calcite.rex.RexInputRef) ArrayList(java.util.ArrayList) List(java.util.List) RexNode(org.apache.calcite.rex.RexNode)

Example 3 with RelTableRef

use of org.apache.calcite.rex.RexTableInputRef.RelTableRef in project calcite by apache.

the class RelMdAllPredicates method getAllPredicates.

/**
 * Add the Join condition to the list obtained from the input.
 */
public RelOptPredicateList getAllPredicates(Join join, RelMetadataQuery mq) {
    if (join.getJoinType() != JoinRelType.INNER) {
        // We cannot map origin of this expression.
        return null;
    }
    final RexBuilder rexBuilder = join.getCluster().getRexBuilder();
    final RexNode pred = join.getCondition();
    final Multimap<List<String>, RelTableRef> qualifiedNamesToRefs = HashMultimap.create();
    RelOptPredicateList newPreds = RelOptPredicateList.EMPTY;
    for (RelNode input : join.getInputs()) {
        final RelOptPredicateList inputPreds = mq.getAllPredicates(input);
        if (inputPreds == null) {
            // Bail out
            return null;
        }
        // Gather table references
        final Set<RelTableRef> tableRefs = mq.getTableReferences(input);
        if (input == join.getLeft()) {
            // Left input references remain unchanged
            for (RelTableRef leftRef : tableRefs) {
                qualifiedNamesToRefs.put(leftRef.getQualifiedName(), leftRef);
            }
            newPreds = newPreds.union(rexBuilder, inputPreds);
        } else {
            // Right input references might need to be updated if there are table name
            // clashes with left input
            final Map<RelTableRef, RelTableRef> currentTablesMapping = new HashMap<>();
            for (RelTableRef rightRef : tableRefs) {
                int shift = 0;
                Collection<RelTableRef> lRefs = qualifiedNamesToRefs.get(rightRef.getQualifiedName());
                if (lRefs != null) {
                    shift = lRefs.size();
                }
                currentTablesMapping.put(rightRef, RelTableRef.of(rightRef.getTable(), shift + rightRef.getEntityNumber()));
            }
            final List<RexNode> updatedPreds = Lists.newArrayList(Iterables.transform(inputPreds.pulledUpPredicates, new Function<RexNode, RexNode>() {

                @Override
                public RexNode apply(RexNode e) {
                    return RexUtil.swapTableReferences(rexBuilder, e, currentTablesMapping);
                }
            }));
            newPreds = newPreds.union(rexBuilder, RelOptPredicateList.of(rexBuilder, updatedPreds));
        }
    }
    // Extract input fields referenced by Join condition
    final Set<RelDataTypeField> inputExtraFields = new LinkedHashSet<>();
    final RelOptUtil.InputFinder inputFinder = new RelOptUtil.InputFinder(inputExtraFields);
    pred.accept(inputFinder);
    final ImmutableBitSet inputFieldsUsed = inputFinder.inputBitSet.build();
    // Infer column origin expressions for given references
    final Map<RexInputRef, Set<RexNode>> mapping = new LinkedHashMap<>();
    for (int idx : inputFieldsUsed) {
        final RexInputRef inputRef = RexInputRef.of(idx, join.getRowType().getFieldList());
        final Set<RexNode> originalExprs = mq.getExpressionLineage(join, inputRef);
        if (originalExprs == null) {
            // Bail out
            return null;
        }
        final RexInputRef ref = RexInputRef.of(idx, join.getRowType().getFieldList());
        mapping.put(ref, originalExprs);
    }
    // Replace with new expressions and return union of predicates
    return newPreds.union(rexBuilder, RelOptPredicateList.of(rexBuilder, RelMdExpressionLineage.createAllPossibleExpressions(rexBuilder, pred, mapping)));
}
Also used : LinkedHashSet(java.util.LinkedHashSet) LinkedHashSet(java.util.LinkedHashSet) ImmutableBitSet(org.apache.calcite.util.ImmutableBitSet) Set(java.util.Set) ImmutableBitSet(org.apache.calcite.util.ImmutableBitSet) HashMap(java.util.HashMap) LinkedHashMap(java.util.LinkedHashMap) LinkedHashMap(java.util.LinkedHashMap) Function(com.google.common.base.Function) RelOptPredicateList(org.apache.calcite.plan.RelOptPredicateList) RexBuilder(org.apache.calcite.rex.RexBuilder) RelOptPredicateList(org.apache.calcite.plan.RelOptPredicateList) List(java.util.List) RelOptUtil(org.apache.calcite.plan.RelOptUtil) RelTableRef(org.apache.calcite.rex.RexTableInputRef.RelTableRef) RelDataTypeField(org.apache.calcite.rel.type.RelDataTypeField) RelNode(org.apache.calcite.rel.RelNode) RexInputRef(org.apache.calcite.rex.RexInputRef) RexNode(org.apache.calcite.rex.RexNode)

Example 4 with RelTableRef

use of org.apache.calcite.rex.RexTableInputRef.RelTableRef 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 5 with RelTableRef

use of org.apache.calcite.rex.RexTableInputRef.RelTableRef in project calcite by apache.

the class AbstractMaterializedViewRule method compensatePartial.

/**
 * It checks whether the target can be rewritten using the source even though the
 * source uses additional tables. In order to do that, we need to double-check
 * that every join that exists in the source and is not in the target is a
 * cardinality-preserving join, i.e., it only appends columns to the row
 * without changing its multiplicity. Thus, the join needs to be:
 * <ul>
 * <li> Equi-join </li>
 * <li> Between all columns in the keys </li>
 * <li> Foreign-key columns do not allow NULL values </li>
 * <li> Foreign-key </li>
 * <li> Unique-key </li>
 * </ul>
 *
 * <p>If it can be rewritten, it returns true. Further, it inserts the missing equi-join
 * predicates in the input {@code compensationEquiColumns} multimap if it is provided.
 * If it cannot be rewritten, it returns false.
 */
private static boolean compensatePartial(Set<RelTableRef> sourceTableRefs, EquivalenceClasses sourceEC, Set<RelTableRef> targetTableRefs, Multimap<RexTableInputRef, RexTableInputRef> compensationEquiColumns) {
    // Create UK-FK graph with view tables
    final DirectedGraph<RelTableRef, Edge> graph = DefaultDirectedGraph.create(Edge.FACTORY);
    final Multimap<List<String>, RelTableRef> tableVNameToTableRefs = ArrayListMultimap.create();
    final Set<RelTableRef> extraTableRefs = new HashSet<>();
    for (RelTableRef tRef : sourceTableRefs) {
        // Add tables in view as vertices
        graph.addVertex(tRef);
        tableVNameToTableRefs.put(tRef.getQualifiedName(), tRef);
        if (!targetTableRefs.contains(tRef)) {
            // Add to extra tables if table is not part of the query
            extraTableRefs.add(tRef);
        }
    }
    for (RelTableRef tRef : graph.vertexSet()) {
        // Add edges between tables
        List<RelReferentialConstraint> constraints = tRef.getTable().getReferentialConstraints();
        for (RelReferentialConstraint constraint : constraints) {
            Collection<RelTableRef> parentTableRefs = tableVNameToTableRefs.get(constraint.getTargetQualifiedName());
            for (RelTableRef parentTRef : parentTableRefs) {
                boolean canBeRewritten = true;
                Multimap<RexTableInputRef, RexTableInputRef> equiColumns = ArrayListMultimap.create();
                for (int pos = 0; pos < constraint.getNumColumns(); pos++) {
                    int foreignKeyPos = constraint.getColumnPairs().get(pos).source;
                    RelDataType foreignKeyColumnType = tRef.getTable().getRowType().getFieldList().get(foreignKeyPos).getType();
                    RexTableInputRef foreignKeyColumnRef = RexTableInputRef.of(tRef, foreignKeyPos, foreignKeyColumnType);
                    int uniqueKeyPos = constraint.getColumnPairs().get(pos).target;
                    RexTableInputRef uniqueKeyColumnRef = RexTableInputRef.of(parentTRef, uniqueKeyPos, parentTRef.getTable().getRowType().getFieldList().get(uniqueKeyPos).getType());
                    if (!foreignKeyColumnType.isNullable() && sourceEC.getEquivalenceClassesMap().containsKey(uniqueKeyColumnRef) && sourceEC.getEquivalenceClassesMap().get(uniqueKeyColumnRef).contains(foreignKeyColumnRef)) {
                        equiColumns.put(foreignKeyColumnRef, uniqueKeyColumnRef);
                    } else {
                        canBeRewritten = false;
                        break;
                    }
                }
                if (canBeRewritten) {
                    // Add edge FK -> UK
                    Edge edge = graph.getEdge(tRef, parentTRef);
                    if (edge == null) {
                        edge = graph.addEdge(tRef, parentTRef);
                    }
                    edge.equiColumns.putAll(equiColumns);
                }
            }
        }
    }
    // Try to eliminate tables from graph: if we can do it, it means extra tables in
    // view are cardinality-preserving joins
    boolean done = false;
    do {
        List<RelTableRef> nodesToRemove = new ArrayList<>();
        for (RelTableRef tRef : graph.vertexSet()) {
            if (graph.getInwardEdges(tRef).size() == 1 && graph.getOutwardEdges(tRef).isEmpty()) {
                // UK-FK join
                nodesToRemove.add(tRef);
                if (compensationEquiColumns != null && extraTableRefs.contains(tRef)) {
                    // We need to add to compensation columns as the table is not present in the query
                    compensationEquiColumns.putAll(graph.getInwardEdges(tRef).get(0).equiColumns);
                }
            }
        }
        if (!nodesToRemove.isEmpty()) {
            graph.removeAllVertices(nodesToRemove);
        } else {
            done = true;
        }
    } while (!done);
    // are tables present in the query: if they are, we can try to rewrite
    if (!Collections.disjoint(graph.vertexSet(), extraTableRefs)) {
        return false;
    }
    return true;
}
Also used : ArrayList(java.util.ArrayList) RelTableRef(org.apache.calcite.rex.RexTableInputRef.RelTableRef) RelDataType(org.apache.calcite.rel.type.RelDataType) RexTableInputRef(org.apache.calcite.rex.RexTableInputRef) RelReferentialConstraint(org.apache.calcite.rel.RelReferentialConstraint) RelReferentialConstraint(org.apache.calcite.rel.RelReferentialConstraint) RelOptPredicateList(org.apache.calcite.plan.RelOptPredicateList) List(java.util.List) ArrayList(java.util.ArrayList) ImmutableList(com.google.common.collect.ImmutableList) DefaultEdge(org.apache.calcite.util.graph.DefaultEdge) HashSet(java.util.HashSet) LinkedHashSet(java.util.LinkedHashSet)

Aggregations

RelTableRef (org.apache.calcite.rex.RexTableInputRef.RelTableRef)16 RelNode (org.apache.calcite.rel.RelNode)12 List (java.util.List)9 RelOptPredicateList (org.apache.calcite.plan.RelOptPredicateList)8 RexNode (org.apache.calcite.rex.RexNode)8 HashMap (java.util.HashMap)7 ArrayList (java.util.ArrayList)6 HashSet (java.util.HashSet)6 RelMetadataQuery (org.apache.calcite.rel.metadata.RelMetadataQuery)6 RexBuilder (org.apache.calcite.rex.RexBuilder)6 LinkedHashSet (java.util.LinkedHashSet)5 RexTableInputRef (org.apache.calcite.rex.RexTableInputRef)5 Function (com.google.common.base.Function)4 ImmutableList (com.google.common.collect.ImmutableList)4 LinkedHashMap (java.util.LinkedHashMap)4 RelReferentialConstraint (org.apache.calcite.rel.RelReferentialConstraint)4 RexInputRef (org.apache.calcite.rex.RexInputRef)4 ImmutableBitSet (org.apache.calcite.util.ImmutableBitSet)4 Test (org.junit.Test)4 Set (java.util.Set)3