All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.org.apache.calcite.plan.volcano.OptimizeTask Maven / Gradle / Ivy

There is a newer version: 5.4.0
Show newest version
/*
 * 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 com.hazelcast.com.liance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.com.hazelcast.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 com.hazelcast.org.apache.calcite.plan.volcano;

import com.hazelcast.org.apache.calcite.plan.Convention;
import com.hazelcast.org.apache.calcite.plan.ConventionTraitDef;
import com.hazelcast.org.apache.calcite.plan.DeriveMode;
import com.hazelcast.org.apache.calcite.plan.RelTrait;
import com.hazelcast.org.apache.calcite.plan.RelTraitSet;
import com.hazelcast.org.apache.calcite.rel.PhysicalNode;
import com.hazelcast.org.apache.calcite.rel.RelNode;
import com.hazelcast.org.apache.calcite.util.trace.CalciteTrace;

import com.hazelcast.org.apiguardian.api.API;
import com.hazelcast.org.slf4j.Logger;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * OptimizeTask represents the optimization task
 * of VolcanoPlanner.
 *
 * 

How does it work?

* *

Let S# denote the seed physical operator in a RelSet after * logical and physical rules transformation, P# denote the * physical operator generated by passing down parent trait * requirements, D# denote the physical operator generated by * deriving from child delivered traitSets.

* * The initial rel list state in a RelSet is as follows: *
 *  cursor
 *    |
 *    V
 *   S1, S2
 * 
* * When we create a task for RelSubset1, the task will immediately * pass the subset's traitSet to seed operators, S1 and S2, * now we have: *
 *  cursor
 *    |
 *    V
 *   S1, S2, P1, P2
 * 
* * The subset task will create a optimization task for the relnode * pointed by cursor, and move cursor to next available physical * operator S2. In the task for S1, it will continue optimize its * child nodes, which are RelSubsets. After child inputs optimization * is finished, S1 will derive new relnodes from delivered subsets * in input RelSet. Once task for S1 is com.hazelcast.com.leted, we have: *
 *      cursor
 *        |
 *        V
 *   S1, S2, P1, P2, D1
 * 
* * The subset task continues scheduling task for S2, P1... until * there is no more relnode created for the RelSet, then we have: *
 *                              cursor
 *                                |
 *                                V
 *   S1, S2, P1, P2, D1, D2, D3, null
 * 
* * When a task for another RelSubset2 is created, the task will try * to pass down the subset's traitSet to seed operator S1 and S2, * now the RelSet looks like: *
 *                              cursor
 *                                |
 *                                V
 *   S1, S2, P1, P2, D1, D2, D3, P3, P4
 * 
* * The process continues till there is no more subsets or relnodes * created for the RelSet. */ @API(since = "1.23", status = API.Status.INTERNAL) abstract class OptimizeTask { static final Logger LOGGER = CalciteTrace.getPlannerTaskTracer(); static OptimizeTask create(RelNode node) { if (node instanceof RelSubset) { return new RelSubsetOptTask((RelSubset) node); } return new RelNodeOptTask(node); } final VolcanoPlanner planner; final int id; OptimizeTask(RelNode node) { planner = (VolcanoPlanner) node.getCluster().getPlanner(); id = planner.nextTaskId++; LOGGER.debug("Scheduled task(id={}) for {}", id, node); } abstract boolean hasSubTask(); abstract OptimizeTask nextSubTask(); abstract void execute(); /** * Task State */ public enum State { SCHEDULED, EXECUTING, COMPLETED } /** * Task for optimizing RelNode. * *

Optimize input RelSubsets and derive new RelNodes * from child traitSets.

*/ static class RelNodeOptTask extends OptimizeTask { final RelNode node; int nextId = 0; // next child index RelNodeOptTask(RelNode node) { super(node); this.node = node; } @Override boolean hasSubTask() { int size = node.getInputs().size(); while (nextId < size) { RelSubset subset = (RelSubset) node.getInput(nextId); if (subset.taskState == null) { // not yet scheduled return true; } else { // maybe a cycle if it is not com.hazelcast.com.leted nextId++; } } return false; } @Override OptimizeTask nextSubTask() { RelNode child = node.getInput(nextId++); return new RelSubsetOptTask((RelSubset) child); } @Override void execute() { if (!(node instanceof PhysicalNode) || ((PhysicalNode) node).getDeriveMode() == DeriveMode.PROHIBITED || !planner.isSeedNode(node)) { LOGGER.debug("Completed task(id={}) for {}", id, node); return; } PhysicalNode rel = (PhysicalNode) node; DeriveMode mode = rel.getDeriveMode(); int arity = node.getInputs().size(); // for OMAKASE List> inputTraits = new ArrayList<>(arity); for (int i = 0; i < arity; i++) { int childId = i; if (mode == DeriveMode.RIGHT_FIRST) { childId = arity - i - 1; } RelSubset input = (RelSubset) node.getInput(childId); List traits = new ArrayList<>(); inputTraits.add(traits); final int numSubset = input.set.subsets.size(); for (int j = 0; j < numSubset; j++) { RelSubset subset = input.set.subsets.get(j); if (!subset.isDelivered() || equalsSansConvention( subset.getTraitSet(), rel.getCluster().traitSet())) { // TODO: should use matching type to determine // Ideally we should stop deriving new relnodes when the // subset's traitSet equals with input traitSet, but // in case someone manually builds a physical relnode // tree, which is highly discouraged, without specifying // correct traitSet, e.g. // EnumerableFilter [].ANY // -> EnumerableMergeJoin [a].Hash[a] // We should still be able to derive the correct traitSet // for the dumb filter, even though the filter's traitSet // should be derived from the MergeJoin when it is created. // But if the subset's traitSet equals with the default // empty traitSet sans convention (the default traitSet // from cluster may have logical convention, NONE, which // is not interesting), we are safe to ignore it, because // a physical filter with non default traitSet, but has a // input with default empty traitSet, e.g. // EnumerableFilter [a].Hash[a] // -> EnumerableProject [].ANY // is definitely wrong, we should fail fast. continue; } if (mode == DeriveMode.OMAKASE) { traits.add(subset.getTraitSet()); } else { RelNode newRel = rel.derive(subset.getTraitSet(), childId); if (newRel != null && !planner.isRegistered(newRel)) { RelSubset relSubset = planner.register(newRel, node); assert relSubset.set == planner.getSubset(node).set; } } } if (mode == DeriveMode.LEFT_FIRST || mode == DeriveMode.RIGHT_FIRST) { break; } } if (mode == DeriveMode.OMAKASE) { List relList = rel.derive(inputTraits); for (RelNode relNode : relList) { if (!planner.isRegistered(relNode)) { planner.register(relNode, node); } } } LOGGER.debug("Completed task(id={}) for {}", id, node); } /** * Returns whether the 2 traitSets are equal without Convention. * It assumes they have the same traitDefs order. */ private boolean equalsSansConvention(RelTraitSet ts1, RelTraitSet ts2) { assert ts1.size() == ts2.size(); for (int i = 0; i < ts1.size(); i++) { RelTrait trait = ts1.getTrait(i); if (trait.getTraitDef() == ConventionTraitDef.INSTANCE) { continue; } if (!trait.equals(ts2.getTrait(i))) { return false; } } return true; } @Override public String toString() { return "Task#" + id + ":{ " + node + " }"; } } /** * Task for optimizing RelSubset. * *

Pass down the trait requirements of current RelSubset * and add enforcers to the new delivered subsets.

*/ static class RelSubsetOptTask extends OptimizeTask { final RelSubset subset; final Set deliveredSubsets = new HashSet<>(); RelSubsetOptTask(RelSubset subset) { super(subset); this.subset = subset; subset.taskState = State.SCHEDULED; propagateTraits(); } private void propagateTraits() { int size = subset.set.getSeedSize(); for (int i = 0; i < size; i++) { RelNode rel = subset.set.rels.get(i); if (!(rel instanceof PhysicalNode) || rel.getConvention() == Convention.NONE || rel.getTraitSet().satisfies(subset.getTraitSet())) { continue; } RelNode node = ((PhysicalNode) rel).passThrough( subset.getTraitSet()); if (node != null && !planner.isRegistered(node)) { RelSubset newSubset = planner.register(node, subset); deliveredSubsets.add(newSubset); } else { // TODO: should we consider stop trying propagation on node // with the same traitset as phyNode? assert true; } } } @Override boolean hasSubTask() { return subset.set.hasNextPhysicalNode(); } @Override OptimizeTask nextSubTask() { RelNode rel = subset.set.nextPhysicalNode(); return new RelNodeOptTask(rel); } @Override void execute() { subset.taskState = State.EXECUTING; subset.set.addConverters(subset, true, false); for (RelSubset delivered : deliveredSubsets) { subset.set.addConverters(delivered, false, false); } subset.taskState = State.COMPLETED; LOGGER.debug("Completed task(id={}) for {}", id, subset); } @Override public String toString() { return "Task#" + id + ":{ " + subset + " }"; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy