Search in sources :

Example 1 with BranchNode

use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.

the class PlanAssembler method setupForNewPlans.

/**
     * Clear any old state and get ready to plan a new plan. The next call to
     * getNextPlan() will return the first candidate plan for these parameters.
     *
     */
private void setupForNewPlans(AbstractParsedStmt parsedStmt) {
    m_bestAndOnlyPlanWasGenerated = false;
    m_partitioning.analyzeTablePartitioning(parsedStmt.allScans());
    if (parsedStmt instanceof ParsedUnionStmt) {
        m_parsedUnion = (ParsedUnionStmt) parsedStmt;
        return;
    }
    if (parsedStmt instanceof ParsedSelectStmt) {
        if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
            throw new PlanningErrorException("Illegal to read a stream.");
        }
        m_parsedSelect = (ParsedSelectStmt) parsedStmt;
        // Simplify the outer join if possible
        if (m_parsedSelect.m_joinTree instanceof BranchNode) {
            if (!m_parsedSelect.hasJoinOrder()) {
                simplifyOuterJoin((BranchNode) m_parsedSelect.m_joinTree);
            }
            // Convert RIGHT joins to the LEFT ones
            ((BranchNode) m_parsedSelect.m_joinTree).toLeftJoin();
        }
        m_subAssembler = new SelectSubPlanAssembler(m_catalogDb, m_parsedSelect, m_partitioning);
        // Process the GROUP BY information, decide whether it is group by the partition column
        if (isPartitionColumnInGroupbyList(m_parsedSelect.groupByColumns())) {
            m_parsedSelect.setHasPartitionColumnInGroupby();
        }
        if (isPartitionColumnInWindowedAggregatePartitionByList()) {
            m_parsedSelect.setHasPartitionColumnInWindowedAggregate();
        }
        return;
    }
    // check that no modification happens to views
    if (tableListIncludesReadOnlyView(parsedStmt.m_tableList)) {
        throw new PlanningErrorException("Illegal to modify a materialized view.");
    }
    m_partitioning.setIsDML();
    // figure out which table we're updating/deleting
    if (parsedStmt instanceof ParsedSwapStmt) {
        assert (parsedStmt.m_tableList.size() == 2);
        if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
            throw new PlanningErrorException("Illegal to swap a stream.");
        }
        m_parsedSwap = (ParsedSwapStmt) parsedStmt;
        return;
    }
    Table targetTable = parsedStmt.m_tableList.get(0);
    if (targetTable.getIsreplicated()) {
        if (m_partitioning.wasSpecifiedAsSingle() && !m_partitioning.isReplicatedDmlToRunOnAllPartitions()) {
            String msg = "Trying to write to replicated table '" + targetTable.getTypeName() + "' in a single-partition procedure.";
            throw new PlanningErrorException(msg);
        }
    } else if (m_partitioning.wasSpecifiedAsSingle() == false) {
        m_partitioning.setPartitioningColumnForDML(targetTable.getPartitioncolumn());
    }
    if (parsedStmt instanceof ParsedInsertStmt) {
        m_parsedInsert = (ParsedInsertStmt) parsedStmt;
        // The currently handled inserts are too simple to even require a subplan assembler. So, done.
        return;
    }
    if (parsedStmt instanceof ParsedUpdateStmt) {
        if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
            throw new PlanningErrorException("Illegal to update a stream.");
        }
        m_parsedUpdate = (ParsedUpdateStmt) parsedStmt;
    } else if (parsedStmt instanceof ParsedDeleteStmt) {
        if (tableListIncludesExportOnly(parsedStmt.m_tableList)) {
            throw new PlanningErrorException("Illegal to delete from a stream.");
        }
        m_parsedDelete = (ParsedDeleteStmt) parsedStmt;
    } else {
        throw new RuntimeException("Unknown subclass of AbstractParsedStmt.");
    }
    if (!m_partitioning.wasSpecifiedAsSingle()) {
        //TODO: When updates and deletes can contain joins, this step may have to be
        // deferred so that the valueEquivalence set can be analyzed per join order.
        // This appears to be an unfortunate side effect of how the HSQL interface
        // misleadingly organizes the placement of join/where filters on the statement tree.
        // This throws off the accounting of equivalence join filters until they can be
        // normalized in analyzeJoinFilters, but that normalization process happens on a
        // per-join-order basis, and so, so must this analysis.
        HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence = parsedStmt.analyzeValueEquivalence();
        Collection<StmtTableScan> scans = parsedStmt.allScans();
        m_partitioning.analyzeForMultiPartitionAccess(scans, valueEquivalence);
    }
    m_subAssembler = new WriterSubPlanAssembler(m_catalogDb, parsedStmt, m_partitioning);
}
Also used : Table(org.voltdb.catalog.Table) Set(java.util.Set) NavigableSet(java.util.NavigableSet) HashSet(java.util.HashSet) StmtTableScan(org.voltdb.planner.parseinfo.StmtTableScan) BranchNode(org.voltdb.planner.parseinfo.BranchNode) AbstractExpression(org.voltdb.expressions.AbstractExpression)

Example 2 with BranchNode

use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.

the class SelectSubPlanAssembler method generateJoinOrdersForTree.

private static List<JoinNode> generateJoinOrdersForTree(JoinNode subTree) {
    if (subTree instanceof BranchNode) {
        BranchNode branchSubTree = (BranchNode) subTree;
        JoinType joinType = branchSubTree.getJoinType();
        if (joinType == JoinType.INNER) {
            return generateInnerJoinOrdersForTree(subTree);
        } else if (joinType == JoinType.LEFT) {
            return generateOuterJoinOrdersForTree(subTree);
        } else if (joinType == JoinType.FULL) {
            return generateFullJoinOrdersForTree(subTree);
        } else {
            // Shouldn't get there
            assert (false);
            return null;
        }
    } else {
        // Single tables and subqueries
        return generateInnerJoinOrdersForTree(subTree);
    }
}
Also used : BranchNode(org.voltdb.planner.parseinfo.BranchNode) JoinType(org.voltdb.types.JoinType)

Example 3 with BranchNode

use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.

the class SelectSubPlanAssembler method getSelectSubPlanForJoinNode.

/**
     * Given a specific join node and access path set for inner and outer tables, construct the plan
     * that gives the right tuples.
     *
     * @param joinNode The join node to build the plan for.
     * @param isInnerTable True if the join node is the inner node in the join
     * @return A completed plan-sub-graph that should match the correct tuples from the
     * correct tables.
     */
private AbstractPlanNode getSelectSubPlanForJoinNode(JoinNode joinNode) {
    assert (joinNode != null);
    if (joinNode instanceof BranchNode) {
        BranchNode branchJoinNode = (BranchNode) joinNode;
        // Outer node
        AbstractPlanNode outerScanPlan = getSelectSubPlanForJoinNode(branchJoinNode.getLeftNode());
        if (outerScanPlan == null) {
            return null;
        }
        // Inner Node.
        AbstractPlanNode innerScanPlan = getSelectSubPlanForJoinNode((branchJoinNode).getRightNode());
        if (innerScanPlan == null) {
            return null;
        }
        // Join Node
        IndexSortablePlanNode answer = getSelectSubPlanForJoin(branchJoinNode, outerScanPlan, innerScanPlan);
        // branch node is an inner join.
        if ((answer != null) && (branchJoinNode.getJoinType() == JoinType.INNER) && outerScanPlan instanceof IndexSortablePlanNode) {
            IndexUseForOrderBy indexUseForJoin = answer.indexUse();
            IndexUseForOrderBy indexUseFromScan = ((IndexSortablePlanNode) outerScanPlan).indexUse();
            indexUseForJoin.setWindowFunctionUsesIndex(indexUseFromScan.getWindowFunctionUsesIndex());
            indexUseForJoin.setWindowFunctionIsCompatibleWithOrderBy(indexUseFromScan.isWindowFunctionCompatibleWithOrderBy());
            indexUseForJoin.setFinalExpressionOrderFromIndexScan(indexUseFromScan.getFinalExpressionOrderFromIndexScan());
            indexUseForJoin.setSortOrderFromIndexScan(indexUseFromScan.getSortOrderFromIndexScan());
        }
        if (answer == null) {
            return null;
        }
        return answer.planNode();
    }
    // End of recursion
    AbstractPlanNode scanNode = getAccessPlanForTable(joinNode);
    // Connect the sub-query tree if any
    if (joinNode instanceof SubqueryLeafNode) {
        StmtSubqueryScan tableScan = ((SubqueryLeafNode) joinNode).getSubqueryScan();
        CompiledPlan subQueryPlan = tableScan.getBestCostPlan();
        assert (subQueryPlan != null);
        assert (subQueryPlan.rootPlanGraph != null);
        // The sub-query best cost plan needs to be un-linked from the previous parent plan
        // it's the same child plan that gets re-attached to many parents one at a time
        subQueryPlan.rootPlanGraph.disconnectParents();
        scanNode.addAndLinkChild(subQueryPlan.rootPlanGraph);
    }
    return scanNode;
}
Also used : AbstractPlanNode(org.voltdb.plannodes.AbstractPlanNode) IndexSortablePlanNode(org.voltdb.plannodes.IndexSortablePlanNode) StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) BranchNode(org.voltdb.planner.parseinfo.BranchNode) SubqueryLeafNode(org.voltdb.planner.parseinfo.SubqueryLeafNode) IndexUseForOrderBy(org.voltdb.plannodes.IndexUseForOrderBy)

Example 4 with BranchNode

use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.

the class SelectSubPlanAssembler method getSelectSubPlanForJoin.

/**
     * Given a join node and plan-sub-graph for outer and inner sub-nodes,
     * construct the plan-sub-graph for that node.
     *
     * @param joinNode A parent join node.
     * @param outerPlan The outer node plan-sub-graph.
     * @param innerPlan The inner node plan-sub-graph.
     * @return A completed plan-sub-graph
     * or null if a valid plan can not be produced for given access paths.
     */
private IndexSortablePlanNode getSelectSubPlanForJoin(BranchNode joinNode, AbstractPlanNode outerPlan, AbstractPlanNode innerPlan) {
    // Filter (post-join) expressions
    ArrayList<AbstractExpression> whereClauses = new ArrayList<>();
    whereClauses.addAll(joinNode.m_whereInnerList);
    whereClauses.addAll(joinNode.m_whereInnerOuterList);
    if (joinNode.getJoinType() == JoinType.FULL) {
        // For all other join types, the whereOuterList expressions were pushed down to the outer node
        whereClauses.addAll(joinNode.m_whereOuterList);
    }
    assert (joinNode.getRightNode() != null);
    JoinNode innerJoinNode = joinNode.getRightNode();
    AccessPath innerAccessPath = innerJoinNode.m_currentAccessPath;
    // We may need to add a send/receive pair to the inner plan for the special case.
    // This trick only works once per plan, BUT once the partitioned data has been
    // received on the coordinator, it can be treated as replicated data in later
    // joins, which MAY help with later outer joins with replicated data.
    boolean needInnerSendReceive = m_partitioning.requiresTwoFragments() && !innerPlan.hasReplicatedResult() && outerPlan.hasReplicatedResult() && joinNode.getJoinType() != JoinType.INNER;
    // When the inner plan is an IndexScan, there MAY be a choice of whether to join using a
    // NestLoopJoin (NLJ) or a NestLoopIndexJoin (NLIJ). The NLJ will have an advantage over the
    // NLIJ in the cases where it applies, since it does a single access or iteration over the index
    // and caches the result, where the NLIJ does an index access or iteration for each outer row.
    // The NestLoopJoin applies when the inner IndexScan is driven only by parameter and constant
    // expressions determined at the start of the query. That requires that none of the IndexScan's
    // various expressions that drive the index access may reference columns from the outer row
    // -- they can only reference columns of the index's base table (the indexed expressions)
    // as well as constants and parameters. The IndexScan's "otherExprs" expressions that only
    // drive post-filtering are not an issue since the NestLoopJoin does feature per-outer-tuple
    // post-filtering on each pass over the cached index scan result.
    // The special case of an OUTER JOIN of replicated outer row data with a partitioned inner
    // table requires that the partitioned data be sent to the coordinator prior to the join.
    // This limits the join option to NLJ. The index scan must make a single index access on
    // each partition and cache the result at the coordinator for post-filtering.
    // This requires that the index access be based on parameters or constants only
    // -- the replicated outer row data will only be available later at the coordinator,
    // so it can not drive the per-partition index scan.
    // If the NLJ option is precluded for the usual reason (outer-row-based indexing) AND
    // the NLIJ is precluded by the special case (OUTER JOIN of replicated outer rows and
    // partitioned inner rows) this method returns null, effectively rejecting this indexed
    // access path for the inner node. Other access paths or join orders may prove more successful.
    boolean canHaveNLJ = true;
    boolean canHaveNLIJ = true;
    if (innerPlan instanceof IndexScanPlanNode) {
        if (hasInnerOuterIndexExpression(joinNode.getRightNode().getTableAlias(), innerAccessPath.indexExprs, innerAccessPath.initialExpr, innerAccessPath.endExprs)) {
            canHaveNLJ = false;
        }
    } else {
        canHaveNLIJ = false;
    }
    if (needInnerSendReceive) {
        canHaveNLIJ = false;
    }
    // partition columns
    if (joinNode.getJoinType() == JoinType.FULL && m_partitioning.requiresTwoFragments() && !outerPlan.hasReplicatedResult() && innerPlan.hasReplicatedResult()) {
        canHaveNLIJ = false;
        canHaveNLJ = false;
    }
    AbstractJoinPlanNode ajNode = null;
    if (canHaveNLJ) {
        NestLoopPlanNode nljNode = new NestLoopPlanNode();
        // get all the clauses that join the applicable two tables
        // Copy innerAccessPath.joinExprs to leave it unchanged,
        // avoiding accumulation of redundant expressions when
        // joinClauses gets built up for various alternative plans.
        ArrayList<AbstractExpression> joinClauses = new ArrayList<>(innerAccessPath.joinExprs);
        if ((innerPlan instanceof IndexScanPlanNode) || (innerPlan instanceof NestLoopIndexPlanNode && innerPlan.getChild(0) instanceof MaterializedScanPlanNode)) {
            // InnerPlan is an IndexScan OR an NLIJ of a MaterializedScan
            // (IN LIST) and an IndexScan. In this case, the inner and
            // inner-outer non-index join expressions (if any) are in the
            // indexScan's otherExpr. The former should stay as IndexScanPlan
            // predicates but the latter need to be pulled up into NLJ
            // predicates because the IndexScan is executed once, not once
            // per outer tuple.
            ArrayList<AbstractExpression> otherExprs = new ArrayList<>();
            // PLEASE do not update the "innerAccessPath.otherExprs", it may be reused
            // for other path evaluation on the other outer side join.
            List<AbstractExpression> innerExpr = filterSingleTVEExpressions(innerAccessPath.otherExprs, otherExprs);
            joinClauses.addAll(otherExprs);
            IndexScanPlanNode scanNode = null;
            if (innerPlan instanceof IndexScanPlanNode) {
                scanNode = (IndexScanPlanNode) innerPlan;
            } else {
                assert (innerPlan instanceof NestLoopIndexPlanNode);
                scanNode = ((NestLoopIndexPlanNode) innerPlan).getInlineIndexScan();
            }
            scanNode.setPredicate(innerExpr);
        } else if (innerJoinNode instanceof BranchNode && joinNode.getJoinType() != JoinType.INNER) {
            // If the innerJoinNode is a LEAF node OR if the join type is an INNER join,
            // the conditions that apply to the inner side
            // have been applied as predicates to the inner scan node already.
            // otherExpr of innerAccessPath comes from its parentNode's joinInnerList.
            // For Outer join (LEFT or FULL), it could mean a join predicate on the table of
            // the inner node ONLY, that can not be pushed down.
            joinClauses.addAll(innerAccessPath.otherExprs);
        }
        nljNode.setJoinPredicate(ExpressionUtil.combinePredicates(joinClauses));
        // combine the tails plan graph with the new head node
        nljNode.addAndLinkChild(outerPlan);
        // right child node.
        if (needInnerSendReceive) {
            // This trick only works once per plan.
            if (outerPlan.hasAnyNodeOfClass(AbstractReceivePlanNode.class) || innerPlan.hasAnyNodeOfClass(AbstractReceivePlanNode.class)) {
                return null;
            }
            innerPlan = addSendReceivePair(innerPlan);
        }
        nljNode.addAndLinkChild(innerPlan);
        ajNode = nljNode;
    } else if (canHaveNLIJ) {
        NestLoopIndexPlanNode nlijNode = new NestLoopIndexPlanNode();
        IndexScanPlanNode innerNode = (IndexScanPlanNode) innerPlan;
        // Set IndexScan predicate. The INNER join expressions for a FULL join come from
        // the innerAccessPath.joinExprs and need to be combined with the other join expressions
        innerNode.setPredicate(innerAccessPath.joinExprs, innerAccessPath.otherExprs);
        nlijNode.addInlinePlanNode(innerPlan);
        // combine the tails plan graph with the new head node
        nlijNode.addAndLinkChild(outerPlan);
        ajNode = nlijNode;
    } else {
        m_recentErrorMsg = "Unsupported special case of complex OUTER JOIN between replicated outer table and partitioned inner table.";
        return null;
    }
    ajNode.setJoinType(joinNode.getJoinType());
    ajNode.setPreJoinPredicate(ExpressionUtil.combinePredicates(joinNode.m_joinOuterList));
    ajNode.setWherePredicate(ExpressionUtil.combinePredicates(whereClauses));
    ajNode.resolveSortDirection();
    return ajNode;
}
Also used : AbstractReceivePlanNode(org.voltdb.plannodes.AbstractReceivePlanNode) JoinNode(org.voltdb.planner.parseinfo.JoinNode) IndexScanPlanNode(org.voltdb.plannodes.IndexScanPlanNode) AbstractJoinPlanNode(org.voltdb.plannodes.AbstractJoinPlanNode) ArrayList(java.util.ArrayList) NestLoopPlanNode(org.voltdb.plannodes.NestLoopPlanNode) BranchNode(org.voltdb.planner.parseinfo.BranchNode) AbstractExpression(org.voltdb.expressions.AbstractExpression) MaterializedScanPlanNode(org.voltdb.plannodes.MaterializedScanPlanNode) NestLoopIndexPlanNode(org.voltdb.plannodes.NestLoopIndexPlanNode)

Example 5 with BranchNode

use of org.voltdb.planner.parseinfo.BranchNode in project voltdb by VoltDB.

the class PlanAssembler method simplifyOuterJoinRecursively.

private static void simplifyOuterJoinRecursively(BranchNode joinNode, List<AbstractExpression> exprs) {
    assert (joinNode != null);
    JoinNode leftNode = joinNode.getLeftNode();
    JoinNode rightNode = joinNode.getRightNode();
    if (joinNode.getJoinType() == JoinType.LEFT) {
        // see if the expression is NULL-rejecting for any of them
        if (isNullRejecting(rightNode.generateTableJoinOrder(), exprs)) {
            joinNode.setJoinType(JoinType.INNER);
        }
    } else if (joinNode.getJoinType() == JoinType.RIGHT) {
        // see if the expression is NULL-rejecting for any of them
        if (isNullRejecting(leftNode.generateTableJoinOrder(), exprs)) {
            joinNode.setJoinType(JoinType.INNER);
        }
    } else if (joinNode.getJoinType() == JoinType.FULL) {
        // see if the expression is NULL-rejecting for any of them
        if (isNullRejecting(leftNode.generateTableJoinOrder(), exprs)) {
            joinNode.setJoinType(JoinType.LEFT);
        }
        // see if the expression is NULL-rejecting for any of them
        if (isNullRejecting(rightNode.generateTableJoinOrder(), exprs)) {
            if (JoinType.FULL == joinNode.getJoinType()) {
                joinNode.setJoinType(JoinType.RIGHT);
            } else {
                // LEFT join was just removed
                joinNode.setJoinType(JoinType.INNER);
            }
        }
    }
    // because they simplify both inner and outer nodes.
    if (leftNode.getWhereExpression() != null) {
        exprs.add(leftNode.getWhereExpression());
    }
    if (rightNode.getWhereExpression() != null) {
        exprs.add(rightNode.getWhereExpression());
    }
    // The JOIN expressions (ON) are only applicable
    // to the INNER node of an outer join.
    List<AbstractExpression> exprsForInnerNode = new ArrayList<>(exprs);
    if (leftNode.getJoinExpression() != null) {
        exprsForInnerNode.add(leftNode.getJoinExpression());
    }
    if (rightNode.getJoinExpression() != null) {
        exprsForInnerNode.add(rightNode.getJoinExpression());
    }
    List<AbstractExpression> leftNodeExprs;
    List<AbstractExpression> rightNodeExprs;
    switch(joinNode.getJoinType()) {
        case INNER:
            leftNodeExprs = exprsForInnerNode;
            rightNodeExprs = exprsForInnerNode;
            break;
        case LEFT:
            leftNodeExprs = exprs;
            rightNodeExprs = exprsForInnerNode;
            break;
        case RIGHT:
            leftNodeExprs = exprsForInnerNode;
            rightNodeExprs = exprs;
            break;
        case FULL:
            leftNodeExprs = exprs;
            rightNodeExprs = exprs;
            break;
        default:
            // shouldn't get there
            leftNodeExprs = null;
            rightNodeExprs = null;
            assert (false);
    }
    if (leftNode instanceof BranchNode) {
        simplifyOuterJoinRecursively((BranchNode) leftNode, leftNodeExprs);
    }
    if (rightNode instanceof BranchNode) {
        simplifyOuterJoinRecursively((BranchNode) rightNode, rightNodeExprs);
    }
}
Also used : BranchNode(org.voltdb.planner.parseinfo.BranchNode) AbstractExpression(org.voltdb.expressions.AbstractExpression) JoinNode(org.voltdb.planner.parseinfo.JoinNode) ArrayList(java.util.ArrayList)

Aggregations

BranchNode (org.voltdb.planner.parseinfo.BranchNode)10 AbstractExpression (org.voltdb.expressions.AbstractExpression)7 JoinNode (org.voltdb.planner.parseinfo.JoinNode)7 ArrayList (java.util.ArrayList)5 StmtTableScan (org.voltdb.planner.parseinfo.StmtTableScan)3 JoinType (org.voltdb.types.JoinType)3 Table (org.voltdb.catalog.Table)2 StmtSubqueryScan (org.voltdb.planner.parseinfo.StmtSubqueryScan)2 SubqueryLeafNode (org.voltdb.planner.parseinfo.SubqueryLeafNode)2 HashMap (java.util.HashMap)1 HashSet (java.util.HashSet)1 NavigableSet (java.util.NavigableSet)1 Set (java.util.Set)1 VoltXMLElement (org.hsqldb_voltpatches.VoltXMLElement)1 Constraint (org.voltdb.catalog.Constraint)1 TupleValueExpression (org.voltdb.expressions.TupleValueExpression)1 StmtTargetTableScan (org.voltdb.planner.parseinfo.StmtTargetTableScan)1 TableLeafNode (org.voltdb.planner.parseinfo.TableLeafNode)1 AbstractJoinPlanNode (org.voltdb.plannodes.AbstractJoinPlanNode)1 AbstractPlanNode (org.voltdb.plannodes.AbstractPlanNode)1