Search in sources :

Example 1 with StmtSubqueryScan

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

the class InsertSubPlanAssembler method nextPlan.

@Override
AbstractPlanNode nextPlan() {
    if (m_bestAndOnlyPlanWasGenerated) {
        return null;
    }
    // We may generate a few different plans for the subquery, but by the time
    // we get here, we'll generate only one plan for the INSERT statement itself.
    // Mostly this method exists to check that we can find a valid partitioning
    // for the statement.
    m_bestAndOnlyPlanWasGenerated = true;
    ParsedInsertStmt insertStmt = (ParsedInsertStmt) m_parsedStmt;
    Table targetTable = insertStmt.m_tableList.get(0);
    targetTable.getTypeName();
    StmtSubqueryScan subquery = insertStmt.getSubqueryScan();
    boolean subqueryIsMultiFragment = subquery.getBestCostPlan().rootPlanGraph.hasAnyNodeOfType(PlanNodeType.SEND);
    if (targetTable.getIsreplicated()) {
        // setUpForNewPlans already validates this
        assert (!m_partitioning.wasSpecifiedAsSingle() && !m_partitioning.isInferredSingle());
        // Cannot access any partitioned tables in subquery for replicated table
        if (!subquery.getIsReplicated()) {
            throw new PlanningErrorException("Subquery in " + getSqlType() + " INTO ... SELECT statement may not access " + "partitioned data for insertion into replicated table " + targetTable.getTypeName() + ".");
        }
    } else if (!m_partitioning.wasSpecifiedAsSingle()) {
        if (subqueryIsMultiFragment) {
            // What is the appropriate level of detail for this message?
            m_recentErrorMsg = getSqlType() + " INTO ... SELECT statement subquery is too complex.  " + "Please either simplify the subquery or use a SELECT followed by an INSERT.";
            return null;
        }
        Column partitioningCol = targetTable.getPartitioncolumn();
        if (partitioningCol == null) {
            assert (m_targetIsExportTable);
            m_recentErrorMsg = "The target table for an INSERT INTO ... SELECT statement is an " + "stream with no partitioning column defined.  " + "This is not currently supported.  Please define a " + "partitioning column for this stream to use it with INSERT INTO ... SELECT.";
            return null;
        }
        List<StmtTableScan> tables = new ArrayList<>();
        StmtTargetTableScan stmtTargetTableScan = new StmtTargetTableScan(targetTable);
        tables.add(stmtTargetTableScan);
        tables.add(subquery);
        // Create value equivalence between the partitioning column of the target table
        // and the corresponding expression produced by the subquery.
        HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence = new HashMap<>();
        int i = 0;
        boolean setEquivalenceForPartitioningCol = false;
        for (Column col : insertStmt.m_columns.keySet()) {
            if (partitioningCol.compareTo(col) == 0) {
                List<SchemaColumn> partitioningColumns = stmtTargetTableScan.getPartitioningColumns();
                assert (partitioningColumns.size() == 1);
                AbstractExpression targetPartitionColExpr = partitioningColumns.get(0).getExpression();
                TupleValueExpression selectedExpr = subquery.getOutputExpression(i);
                assert (!valueEquivalence.containsKey(targetPartitionColExpr));
                assert (!valueEquivalence.containsKey(selectedExpr));
                Set<AbstractExpression> equivSet = new HashSet<>();
                equivSet.add(targetPartitionColExpr);
                equivSet.add(selectedExpr);
                valueEquivalence.put(targetPartitionColExpr, equivSet);
                valueEquivalence.put(selectedExpr, equivSet);
                setEquivalenceForPartitioningCol = true;
            }
            ++i;
        }
        if (!setEquivalenceForPartitioningCol) {
            // partitioning column of target table is not being set from value produced by the subquery.
            m_recentErrorMsg = "Partitioning column must be assigned a value " + "produced by the subquery in an " + getSqlType() + " INTO ... SELECT statement.";
            return null;
        }
        m_partitioning.analyzeForMultiPartitionAccess(tables, valueEquivalence);
        if (!m_partitioning.isJoinValid()) {
            m_recentErrorMsg = "Partitioning could not be determined for " + getSqlType() + " INTO ... SELECT statement.  " + "Please ensure that statement does not attempt to copy row data from one partition to another, " + "which is unsupported.";
            return null;
        }
    }
    return subquery.getBestCostPlan().rootPlanGraph;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) TupleValueExpression(org.voltdb.expressions.TupleValueExpression) Table(org.voltdb.catalog.Table) Set(java.util.Set) HashSet(java.util.HashSet) HashMap(java.util.HashMap) AbstractExpression(org.voltdb.expressions.AbstractExpression) Column(org.voltdb.catalog.Column) SchemaColumn(org.voltdb.plannodes.SchemaColumn) StmtTargetTableScan(org.voltdb.planner.parseinfo.StmtTargetTableScan) ArrayList(java.util.ArrayList) List(java.util.List)

Example 2 with StmtSubqueryScan

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

the class AbstractParsedStmt method parseSubqueryExpression.

/**
     * Parse an expression subquery
     */
private SelectSubqueryExpression parseSubqueryExpression(VoltXMLElement exprNode) {
    assert (exprNode.children.size() == 1);
    VoltXMLElement subqueryElmt = exprNode.children.get(0);
    AbstractParsedStmt subqueryStmt = parseSubquery(subqueryElmt);
    // add table to the query cache
    String withoutAlias = null;
    StmtSubqueryScan stmtSubqueryScan = addSubqueryToStmtCache(subqueryStmt, withoutAlias);
    // Set to the default SELECT_SUBQUERY. May be overridden depending on the context
    return new SelectSubqueryExpression(ExpressionType.SELECT_SUBQUERY, stmtSubqueryScan);
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) VoltXMLElement(org.hsqldb_voltpatches.VoltXMLElement) SelectSubqueryExpression(org.voltdb.expressions.SelectSubqueryExpression)

Example 3 with StmtSubqueryScan

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

the class StatementPartitioning method analyzeForMultiPartitionAccess.

/**
     * Given the query's list of tables and its collection(s) of equality-filtered columns and their equivalents,
     * determine whether all joins involving partitioned tables can be executed locally on a single partition.
     * This is only the case when they include equality comparisons between partition key columns.
     * VoltDB will reject joins of multiple partitioned tables unless all their partition keys are
     * constrained to be equal to each other.
     * Example: select * from T1, T2 where T1.ID = T2.ID
     * Additionally, in this case, there may be a constant equality filter on any of the columns,
     * which we want to extract as our SP partitioning parameter.
     *
     * @param tableAliasList The tables.
     * @param valueEquivalence Their column equality filters
     * @return the number of independently partitioned tables
     *         -- partitioned tables that aren't joined or filtered by the same value.
     *         The caller can raise an alarm if there is more than one.
     */
public void analyzeForMultiPartitionAccess(Collection<StmtTableScan> scans, HashMap<AbstractExpression, Set<AbstractExpression>> valueEquivalence) {
    //* enable to debug */ System.out.println("DEBUG: analyze4MPAccess w/ scans:" + scans.size() + " filters:" + valueEquivalence.size());
    TupleValueExpression tokenPartitionKey = null;
    Set<Set<AbstractExpression>> eqSets = new HashSet<Set<AbstractExpression>>();
    int unfilteredPartitionKeyCount = 0;
    // reset this flag to forget the last result of the multiple partition access path.
    // AdHoc with parameters will call this function at least two times
    // By default this flag should be true.
    setJoinValid(true);
    setJoinInvalidReason(null);
    boolean subqueryHasReceiveNode = false;
    boolean hasPartitionedTableJoin = false;
    // Iterate over the tables to collect partition columns.
    for (StmtTableScan tableScan : scans) {
        // Replicated tables don't need filter coverage.
        if (tableScan.getIsReplicated()) {
            continue;
        }
        // The partition column can be null in an obscure edge case.
        // The table is declared non-replicated yet specifies no partitioning column.
        // This can occur legitimately when views based on partitioned tables neglect to group by the partition column.
        // The interpretation of this edge case is that the table has "randomly distributed data".
        // In such a case, the table is valid for use by MP queries only and can only be joined with replicated tables
        // because it has no recognized partitioning join key.
        List<SchemaColumn> columnsNeedingCoverage = tableScan.getPartitioningColumns();
        if (tableScan instanceof StmtSubqueryScan) {
            StmtSubqueryScan subScan = (StmtSubqueryScan) tableScan;
            subScan.promoteSinglePartitionInfo(valueEquivalence, eqSets);
            CompiledPlan subqueryPlan = subScan.getBestCostPlan();
            if ((!subScan.canRunInOneFragment()) || ((subqueryPlan != null) && subqueryPlan.rootPlanGraph.hasAnyNodeOfClass(AbstractReceivePlanNode.class))) {
                if (subqueryHasReceiveNode) {
                    // Has found another subquery with receive node on the same level
                    // Not going to support this kind of subquery join with 2 fragment plan.
                    setJoinValid(false);
                    setJoinInvalidReason("This multipartition query is not plannable.  " + "It has a subquery which cannot be single partition.");
                    // Still needs to count the independent partition tables
                    break;
                }
                subqueryHasReceiveNode = true;
                if (subScan.isTableAggregate()) {
                    // Any process based on this subquery should require 1 fragment only.
                    continue;
                }
            } else {
                // this subquery partition table without receive node
                hasPartitionedTableJoin = true;
            }
        } else {
            // This table is a partition table
            hasPartitionedTableJoin = true;
        }
        boolean unfiltered = true;
        for (AbstractExpression candidateColumn : valueEquivalence.keySet()) {
            if (!(candidateColumn instanceof TupleValueExpression)) {
                continue;
            }
            TupleValueExpression candidatePartitionKey = (TupleValueExpression) candidateColumn;
            if (!canCoverPartitioningColumn(candidatePartitionKey, columnsNeedingCoverage)) {
                continue;
            }
            unfiltered = false;
            if (tokenPartitionKey == null) {
                tokenPartitionKey = candidatePartitionKey;
            }
            eqSets.add(valueEquivalence.get(candidatePartitionKey));
        }
        if (unfiltered) {
            ++unfilteredPartitionKeyCount;
        }
    }
    // end for each table StmtTableScan in the collection
    m_countOfIndependentlyPartitionedTables = eqSets.size() + unfilteredPartitionKeyCount;
    //* enable to debug */ System.out.println("DEBUG: analyze4MPAccess found: " + m_countOfIndependentlyPartitionedTables + " = " + eqSets.size() + " + " + unfilteredPartitionKeyCount);
    if (m_countOfIndependentlyPartitionedTables > 1) {
        setJoinValid(false);
        setJoinInvalidReason("This query is not plannable.  " + "The planner cannot guarantee that all rows would be in a single partition.");
    }
    // on outer level. Not going to support this kind of join.
    if (subqueryHasReceiveNode && hasPartitionedTableJoin) {
        setJoinValid(false);
        setJoinInvalidReason("This query is not plannable.  It has a subquery which needs cross-partition access.");
    }
    if ((unfilteredPartitionKeyCount == 0) && (eqSets.size() == 1)) {
        for (Set<AbstractExpression> partitioningValues : eqSets) {
            for (AbstractExpression constExpr : partitioningValues) {
                if (constExpr instanceof TupleValueExpression) {
                    continue;
                }
                VoltType valueType = tokenPartitionKey.getValueType();
                addPartitioningExpression(tokenPartitionKey.getTableName() + '.' + tokenPartitionKey.getColumnName(), constExpr, valueType);
                // Only need one constant value.
                break;
            }
        }
    }
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) TupleValueExpression(org.voltdb.expressions.TupleValueExpression) AbstractReceivePlanNode(org.voltdb.plannodes.AbstractReceivePlanNode) Set(java.util.Set) HashSet(java.util.HashSet) SchemaColumn(org.voltdb.plannodes.SchemaColumn) StmtTableScan(org.voltdb.planner.parseinfo.StmtTableScan) AbstractExpression(org.voltdb.expressions.AbstractExpression) VoltType(org.voltdb.VoltType) HashSet(java.util.HashSet)

Example 4 with StmtSubqueryScan

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

the class ParsedUnionStmt method breakUpSetOpSubquery.

/**
     * Break up UNION/INTERSECT (ALL) set ops into individual selects that are part
     * of the IN/EXISTS subquery into multiple expressions for each set op child
     * combined by the conjunction AND/OR expression.
     * col IN ( queryA UNION queryB ) - > col IN (queryA) OR col IN (queryB)
     * col IN ( queryA INTERSECTS queryB ) - > col IN (queryA) AND col IN (queryB)
     * The EXCEPT set op is LEFT as is
     * Also the ALL qualifier is dropped because IN/EXISTS expressions only
     * need just one tuple in the results set
     *
     * @param subqueryExpr - IN/EXISTS expression with a possible SET OP subquery
     * @return simplified expression
     */
protected static AbstractExpression breakUpSetOpSubquery(AbstractExpression expr) {
    assert (expr != null);
    SelectSubqueryExpression subqueryExpr = null;
    if (expr.getExpressionType() == ExpressionType.COMPARE_EQUAL && expr.getRight() instanceof SelectSubqueryExpression) {
        subqueryExpr = (SelectSubqueryExpression) expr.getRight();
    } else if (expr.getExpressionType() == ExpressionType.OPERATOR_EXISTS && expr.getLeft() instanceof SelectSubqueryExpression) {
        subqueryExpr = (SelectSubqueryExpression) expr.getLeft();
    }
    if (subqueryExpr == null) {
        return expr;
    }
    AbstractParsedStmt subquery = subqueryExpr.getSubqueryStmt();
    if (!(subquery instanceof ParsedUnionStmt)) {
        return expr;
    }
    ParsedUnionStmt setOpStmt = (ParsedUnionStmt) subquery;
    if (UnionType.EXCEPT == setOpStmt.m_unionType || UnionType.EXCEPT_ALL == setOpStmt.m_unionType) {
        setOpStmt.m_unionType = UnionType.EXCEPT;
        return expr;
    }
    if (UnionType.UNION_ALL == setOpStmt.m_unionType) {
        setOpStmt.m_unionType = UnionType.UNION;
    } else if (UnionType.INTERSECT_ALL == setOpStmt.m_unionType) {
        setOpStmt.m_unionType = UnionType.INTERSECT;
    }
    ExpressionType conjuctionType = (setOpStmt.m_unionType == UnionType.UNION) ? ExpressionType.CONJUNCTION_OR : ExpressionType.CONJUNCTION_AND;
    AbstractExpression retval = null;
    AbstractParsedStmt parentStmt = subquery.m_parentStmt;
    // It's a subquery which means it must have a parent
    assert (parentStmt != null);
    for (AbstractParsedStmt child : setOpStmt.m_children) {
        // add table to the query cache
        String withoutAlias = null;
        StmtSubqueryScan tableCache = parentStmt.addSubqueryToStmtCache(child, withoutAlias);
        AbstractExpression childSubqueryExpr = new SelectSubqueryExpression(subqueryExpr.getExpressionType(), tableCache);
        AbstractExpression newExpr = null;
        try {
            newExpr = expr.getExpressionType().getExpressionClass().newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage(), e);
        }
        newExpr.setExpressionType(expr.getExpressionType());
        if (ExpressionType.COMPARE_EQUAL == expr.getExpressionType()) {
            newExpr.setLeft(expr.getLeft().clone());
            newExpr.setRight(childSubqueryExpr);
            assert (newExpr instanceof ComparisonExpression);
            ((ComparisonExpression) newExpr).setQuantifier(((ComparisonExpression) expr).getQuantifier());
        } else {
            newExpr.setLeft(childSubqueryExpr);
        }
        // Recurse
        newExpr = ParsedUnionStmt.breakUpSetOpSubquery(newExpr);
        if (retval == null) {
            retval = newExpr;
        } else {
            retval = new ConjunctionExpression(conjuctionType, retval, newExpr);
        }
    }
    return retval;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) ComparisonExpression(org.voltdb.expressions.ComparisonExpression) AbstractExpression(org.voltdb.expressions.AbstractExpression) SelectSubqueryExpression(org.voltdb.expressions.SelectSubqueryExpression) ExpressionType(org.voltdb.types.ExpressionType) ConjunctionExpression(org.voltdb.expressions.ConjunctionExpression)

Example 5 with StmtSubqueryScan

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

the class ParsedInsertStmt method parse.

@Override
void parse(VoltXMLElement stmtNode) {
    // but those table scans will belong to the corresponding ParsedSelectStmt
    assert (m_tableList.isEmpty());
    String tableName = stmtNode.attributes.get("table");
    // Need to add the table to the cache. It may be required to resolve the
    // correlated TVE in case of WHERE clause contains IN subquery
    Table table = getTableFromDB(tableName);
    addTableToStmtCache(table, tableName);
    m_tableList.add(table);
    for (VoltXMLElement node : stmtNode.children) {
        if (node.name.equals("columns")) {
            parseTargetColumns(node, table, m_columns);
        } else if (node.name.equals(SELECT_NODE_NAME)) {
            m_subquery = new StmtSubqueryScan(parseSubquery(node), "__VOLT_INSERT_SUBQUERY__");
            // Until scalar subqueries are allowed in INSERT ... VALUES statements,
            // The top-level SELECT subquery in an INSERT ... SELECT statement
            // is the only possible subselect in an INSERT statement.
            m_scans.add(m_subquery);
        } else if (node.name.equals(UNION_NODE_NAME)) {
            throw new PlanningErrorException("INSERT INTO ... SELECT is not supported for UNION or other set operations.");
        }
    }
    calculateContentDeterminismMessage();
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) Table(org.voltdb.catalog.Table) VoltXMLElement(org.hsqldb_voltpatches.VoltXMLElement)

Aggregations

StmtSubqueryScan (org.voltdb.planner.parseinfo.StmtSubqueryScan)15 AbstractExpression (org.voltdb.expressions.AbstractExpression)8 StmtTableScan (org.voltdb.planner.parseinfo.StmtTableScan)6 Constraint (org.voltdb.catalog.Constraint)5 Table (org.voltdb.catalog.Table)5 SelectSubqueryExpression (org.voltdb.expressions.SelectSubqueryExpression)4 TupleValueExpression (org.voltdb.expressions.TupleValueExpression)4 AbstractPlanNode (org.voltdb.plannodes.AbstractPlanNode)4 SchemaColumn (org.voltdb.plannodes.SchemaColumn)4 HashMap (java.util.HashMap)3 HashSet (java.util.HashSet)3 VoltXMLElement (org.hsqldb_voltpatches.VoltXMLElement)3 Column (org.voltdb.catalog.Column)3 StmtTargetTableScan (org.voltdb.planner.parseinfo.StmtTargetTableScan)3 ArrayList (java.util.ArrayList)2 List (java.util.List)2 Set (java.util.Set)2 ColumnRef (org.voltdb.catalog.ColumnRef)2 BranchNode (org.voltdb.planner.parseinfo.BranchNode)2 SubqueryLeafNode (org.voltdb.planner.parseinfo.SubqueryLeafNode)2