Search in sources :

Example 6 with StmtSubqueryScan

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

the class PlanAssembler method getNextInsertPlan.

/**
     * Get the next (only) plan for a SQL insertion. Inserts are pretty simple
     * and this will only generate a single plan.
     *
     * @return The next (only) plan for a given insert statement, then null.
     */
private CompiledPlan getNextInsertPlan() {
    // do it the right way once, then return null after that
    if (m_bestAndOnlyPlanWasGenerated) {
        return null;
    }
    m_bestAndOnlyPlanWasGenerated = true;
    // figure out which table we're inserting into
    assert (m_parsedInsert.m_tableList.size() == 1);
    Table targetTable = m_parsedInsert.m_tableList.get(0);
    StmtSubqueryScan subquery = m_parsedInsert.getSubqueryScan();
    CompiledPlan retval = null;
    String isContentDeterministic = null;
    if (subquery != null) {
        isContentDeterministic = subquery.calculateContentDeterminismMessage();
        if (subquery.getBestCostPlan() == null) {
            // in getBestCostPlan, above.
            throw new PlanningErrorException("INSERT INTO ... SELECT subquery could not be planned: " + m_recentErrorMsg);
        }
        boolean targetIsExportTable = tableListIncludesExportOnly(m_parsedInsert.m_tableList);
        InsertSubPlanAssembler subPlanAssembler = new InsertSubPlanAssembler(m_catalogDb, m_parsedInsert, m_partitioning, targetIsExportTable);
        AbstractPlanNode subplan = subPlanAssembler.nextPlan();
        if (subplan == null) {
            throw new PlanningErrorException(subPlanAssembler.m_recentErrorMsg);
        }
        assert (m_partitioning.isJoinValid());
        //  Use the subquery's plan as the basis for the insert plan.
        retval = subquery.getBestCostPlan();
    } else {
        retval = new CompiledPlan();
    }
    retval.setReadOnly(false);
    //      for the INSERT ... SELECT ... case, by analyzing the subquery.
    if (m_parsedInsert.m_isUpsert) {
        boolean hasPrimaryKey = false;
        for (Constraint constraint : targetTable.getConstraints()) {
            if (constraint.getType() != ConstraintType.PRIMARY_KEY.getValue()) {
                continue;
            }
            hasPrimaryKey = true;
            boolean targetsPrimaryKey = false;
            for (ColumnRef colRef : constraint.getIndex().getColumns()) {
                int primary = colRef.getColumn().getIndex();
                for (Column targetCol : m_parsedInsert.m_columns.keySet()) {
                    if (targetCol.getIndex() == primary) {
                        targetsPrimaryKey = true;
                        break;
                    }
                }
                if (!targetsPrimaryKey) {
                    throw new PlanningErrorException("UPSERT on table \"" + targetTable.getTypeName() + "\" must specify a value for primary key \"" + colRef.getColumn().getTypeName() + "\".");
                }
            }
        }
        if (!hasPrimaryKey) {
            throw new PlanningErrorException("UPSERT is not allowed on table \"" + targetTable.getTypeName() + "\" that has no primary key.");
        }
    }
    CatalogMap<Column> targetTableColumns = targetTable.getColumns();
    for (Column col : targetTableColumns) {
        boolean needsValue = (!m_parsedInsert.m_isUpsert) && (col.getNullable() == false) && (col.getDefaulttype() == 0);
        if (needsValue && !m_parsedInsert.m_columns.containsKey(col)) {
            // This check could be done during parsing?
            throw new PlanningErrorException("Column " + col.getName() + " has no default and is not nullable.");
        }
        // hint that this statement can be executed SP.
        if (col.equals(m_partitioning.getPartitionColForDML()) && subquery == null) {
            // When AdHoc insert-into-select is supported, we'll need to be able to infer
            // partitioning of the sub-select
            AbstractExpression expr = m_parsedInsert.getExpressionForPartitioning(col);
            String fullColumnName = targetTable.getTypeName() + "." + col.getTypeName();
            m_partitioning.addPartitioningExpression(fullColumnName, expr, expr.getValueType());
        }
    }
    NodeSchema matSchema = null;
    if (subquery == null) {
        matSchema = new NodeSchema();
    }
    int[] fieldMap = new int[m_parsedInsert.m_columns.size()];
    int i = 0;
    //   - For VALUES(...) insert statements, build the materialize node's schema
    for (Map.Entry<Column, AbstractExpression> e : m_parsedInsert.m_columns.entrySet()) {
        Column col = e.getKey();
        fieldMap[i] = col.getIndex();
        if (matSchema != null) {
            AbstractExpression valExpr = e.getValue();
            valExpr.setInBytes(col.getInbytes());
            // Patch over any mismatched expressions with an explicit cast.
            // Most impossible-to-cast type combinations should have already been caught by the
            // parser, but there are also runtime checks in the casting code
            // -- such as for out of range values.
            valExpr = castExprIfNeeded(valExpr, col);
            matSchema.addColumn(AbstractParsedStmt.TEMP_TABLE_NAME, AbstractParsedStmt.TEMP_TABLE_NAME, col.getTypeName(), col.getTypeName(), valExpr);
        }
        i++;
    }
    // the root of the insert plan may be an InsertPlanNode, or
    // it may be a scan plan node.  We may do an inline InsertPlanNode
    // as well.
    InsertPlanNode insertNode = new InsertPlanNode();
    insertNode.setTargetTableName(targetTable.getTypeName());
    if (subquery != null) {
        insertNode.setSourceIsPartitioned(!subquery.getIsReplicated());
    }
    // The field map tells the insert node
    // where to put values produced by child into the row to be inserted.
    insertNode.setFieldMap(fieldMap);
    AbstractPlanNode root = insertNode;
    if (matSchema != null) {
        MaterializePlanNode matNode = new MaterializePlanNode(matSchema);
        // connect the insert and the materialize nodes together
        insertNode.addAndLinkChild(matNode);
        retval.statementGuaranteesDeterminism(false, true, isContentDeterministic);
    } else {
        ScanPlanNodeWithInlineInsert planNode = (retval.rootPlanGraph instanceof ScanPlanNodeWithInlineInsert) ? ((ScanPlanNodeWithInlineInsert) retval.rootPlanGraph) : null;
        // Inline upsert might be possible, but not now.
        if (planNode != null && (!m_parsedInsert.m_isUpsert) && (!planNode.hasInlineAggregateNode())) {
            planNode.addInlinePlanNode(insertNode);
            root = planNode.getAbstractNode();
        } else {
            // Otherwise just make it out-of-line.
            insertNode.addAndLinkChild(retval.rootPlanGraph);
        }
    }
    if (m_partitioning.wasSpecifiedAsSingle() || m_partitioning.isInferredSingle()) {
        insertNode.setMultiPartition(false);
        retval.rootPlanGraph = root;
        return retval;
    }
    insertNode.setMultiPartition(true);
    // Add a compensating sum of modified tuple counts or a limit 1
    // AND a send on top of a union-like receive node.
    boolean isReplicated = targetTable.getIsreplicated();
    retval.rootPlanGraph = addCoordinatorToDMLNode(root, isReplicated);
    return retval;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) AbstractPlanNode(org.voltdb.plannodes.AbstractPlanNode) Table(org.voltdb.catalog.Table) Constraint(org.voltdb.catalog.Constraint) InsertPlanNode(org.voltdb.plannodes.InsertPlanNode) MaterializePlanNode(org.voltdb.plannodes.MaterializePlanNode) Constraint(org.voltdb.catalog.Constraint) AbstractExpression(org.voltdb.expressions.AbstractExpression) Column(org.voltdb.catalog.Column) SchemaColumn(org.voltdb.plannodes.SchemaColumn) ColumnRef(org.voltdb.catalog.ColumnRef) Map(java.util.Map) CatalogMap(org.voltdb.catalog.CatalogMap) HashMap(java.util.HashMap) NodeSchema(org.voltdb.plannodes.NodeSchema)

Example 7 with StmtSubqueryScan

use of org.voltdb.planner.parseinfo.StmtSubqueryScan 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 8 with StmtSubqueryScan

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

the class AbstractParsedStmt method parseTable.

/**
     *
     * @param tableNode
     */
private void parseTable(VoltXMLElement tableNode) {
    String tableName = tableNode.attributes.get("table");
    assert (tableName != null);
    String tableAlias = tableNode.attributes.get("tablealias");
    if (tableAlias == null) {
        tableAlias = tableName;
    }
    // Hsql rejects name conflicts in a single query
    m_tableAliasListAsJoinOrder.add(tableAlias);
    VoltXMLElement subqueryElement = null;
    // Possible sub-query
    for (VoltXMLElement childNode : tableNode.children) {
        if (!childNode.name.equals("tablesubquery")) {
            continue;
        }
        if (childNode.children.isEmpty()) {
            continue;
        }
        // sub-query FROM (SELECT ...)
        subqueryElement = childNode.children.get(0);
        break;
    }
    // add table to the query cache before processing the JOIN/WHERE expressions
    // The order is important because processing sub-query expressions assumes that
    // the sub-query is already registered
    StmtTableScan tableScan = null;
    Table table = null;
    // In case of a subquery we need to preserve its filter expressions
    AbstractExpression simplifiedSubqueryFilter = null;
    if (subqueryElement == null) {
        table = getTableFromDB(tableName);
        assert (table != null);
        tableScan = addTableToStmtCache(table, tableAlias);
        m_tableList.add(table);
    } else {
        AbstractParsedStmt subquery = parseFromSubQuery(subqueryElement);
        StmtSubqueryScan subqueryScan = addSubqueryToStmtCache(subquery, tableAlias);
        tableScan = subqueryScan;
        StmtTargetTableScan simpler = simplifierForSubquery(subquery);
        if (simpler != null) {
            tableScan = addSimplifiedSubqueryToStmtCache(subqueryScan, simpler);
            table = simpler.getTargetTable();
            // Extract subquery's filters
            assert (subquery.m_joinTree != null);
            // Adjust the table alias in all TVEs from the eliminated
            // subquery expressions. Example:
            // SELECT TA2.CA FROM (SELECT C CA FROM T TA1 WHERE C > 0) TA2
            // The table alias TA1 from the original TVE (T)TA1.C from the
            // subquery WHERE condition needs to be replaced with the alias
            // TA2. The new TVE will be (T)TA2.C.
            // The column alias does not require an adjustment.
            simplifiedSubqueryFilter = subquery.m_joinTree.getAllFilters();
            List<TupleValueExpression> tves = ExpressionUtil.getTupleValueExpressions(simplifiedSubqueryFilter);
            for (TupleValueExpression tve : tves) {
                tve.setTableAlias(tableScan.getTableAlias());
                tve.setOrigStmtId(m_stmtId);
            }
        }
    }
    AbstractExpression joinExpr = parseJoinCondition(tableNode);
    AbstractExpression whereExpr = parseWhereCondition(tableNode);
    if (simplifiedSubqueryFilter != null) {
        // Add subqueruy's expressions as JOIN filters to make sure they will
        // stay at the node level in case of an OUTER joins and won't affect
        // the join simplification process:
        // select * from T LEFT JOIN (select C FROM T1 WHERE C > 2) S ON T.C = S.C;
        joinExpr = (joinExpr != null) ? ExpressionUtil.combine(joinExpr, simplifiedSubqueryFilter) : simplifiedSubqueryFilter;
    }
    // The join type of the leaf node is always INNER
    // For a new tree its node's ids start with 0 and keep incrementing by 1
    int nodeId = (m_joinTree == null) ? 0 : m_joinTree.getId() + 1;
    JoinNode leafNode;
    if (table != null) {
        assert (tableScan instanceof StmtTargetTableScan);
        leafNode = new TableLeafNode(nodeId, joinExpr, whereExpr, (StmtTargetTableScan) tableScan);
    } else {
        assert (tableScan instanceof StmtSubqueryScan);
        leafNode = new SubqueryLeafNode(nodeId, joinExpr, whereExpr, (StmtSubqueryScan) tableScan);
        leafNode.updateContentDeterminismMessage(((StmtSubqueryScan) tableScan).calculateContentDeterminismMessage());
    }
    if (m_joinTree == null) {
        // this is the first table
        m_joinTree = leafNode;
    } else {
        // Build the tree by attaching the next table always to the right
        // The node's join type is determined by the type of its right node
        JoinType joinType = JoinType.get(tableNode.attributes.get("jointype"));
        assert (joinType != JoinType.INVALID);
        JoinNode joinNode = new BranchNode(nodeId + 1, joinType, m_joinTree, leafNode);
        m_joinTree = joinNode;
    }
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) TupleValueExpression(org.voltdb.expressions.TupleValueExpression) Table(org.voltdb.catalog.Table) TableLeafNode(org.voltdb.planner.parseinfo.TableLeafNode) JoinNode(org.voltdb.planner.parseinfo.JoinNode) JoinType(org.voltdb.types.JoinType) VoltXMLElement(org.hsqldb_voltpatches.VoltXMLElement) Constraint(org.voltdb.catalog.Constraint) StmtTableScan(org.voltdb.planner.parseinfo.StmtTableScan) BranchNode(org.voltdb.planner.parseinfo.BranchNode) AbstractExpression(org.voltdb.expressions.AbstractExpression) SubqueryLeafNode(org.voltdb.planner.parseinfo.SubqueryLeafNode) StmtTargetTableScan(org.voltdb.planner.parseinfo.StmtTargetTableScan)

Example 9 with StmtSubqueryScan

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

the class AbstractParsedStmt method orderByColumnsCoverUniqueKeys.

/**
     * Order by Columns or expressions has to operate on the display columns or expressions.
     * @return
     */
protected boolean orderByColumnsCoverUniqueKeys() {
    // In theory, if EVERY table in the query has a uniqueness constraint
    // (primary key or other unique index) on columns that are all listed in the ORDER BY values,
    // the result is deterministic.
    // This holds regardless of whether the associated index is actually used in the selected plan,
    // so this check is plan-independent.
    //
    // baseTableAliases associates table aliases with the order by
    // expressions which reference them.  Presumably by using
    // table aliases we will map table scans to expressions rather
    // than tables to expressions, and not confuse ourselves with
    // different instances of the same table in self joins.
    HashMap<String, List<AbstractExpression>> baseTableAliases = new HashMap<>();
    for (ParsedColInfo col : orderByColumns()) {
        AbstractExpression expr = col.expression;
        //
        // Compute the set of tables mentioned in the expression.
        //   1. Search out all the TVEs.
        //   2. Throw the aliases of the tables of each of these into a HashSet.
        //      The table must have an alias.  It might not have a name.
        //   3. If the HashSet has size > 1 we can't use this expression.
        //
        List<TupleValueExpression> baseTVEExpressions = expr.findAllTupleValueSubexpressions();
        Set<String> baseTableNames = new HashSet<>();
        for (TupleValueExpression tve : baseTVEExpressions) {
            String tableAlias = tve.getTableAlias();
            assert (tableAlias != null);
            baseTableNames.add(tableAlias);
        }
        if (baseTableNames.size() != 1) {
            // Neither are (nonsense) constant (table-less) expressions.
            continue;
        }
        // Everything in the baseTVEExpressions table is a column
        // in the same table and has the same alias. So just grab the first one.
        // All we really want is the alias.
        AbstractExpression baseTVE = baseTVEExpressions.get(0);
        String nextTableAlias = ((TupleValueExpression) baseTVE).getTableAlias();
        // and disappear.
        assert (nextTableAlias != null);
        List<AbstractExpression> perTable = baseTableAliases.get(nextTableAlias);
        if (perTable == null) {
            perTable = new ArrayList<>();
            baseTableAliases.put(nextTableAlias, perTable);
        }
        perTable.add(expr);
    }
    if (m_tableAliasMap.size() > baseTableAliases.size()) {
        //        like Unique Index nested loop join, etc.
        return false;
    }
    boolean allScansAreDeterministic = true;
    for (Entry<String, List<AbstractExpression>> orderedAlias : baseTableAliases.entrySet()) {
        List<AbstractExpression> orderedAliasExprs = orderedAlias.getValue();
        StmtTableScan tableScan = getStmtTableScanByAlias(orderedAlias.getKey());
        if (tableScan == null) {
            assert (false);
            return false;
        }
        if (tableScan instanceof StmtSubqueryScan) {
            // don't yet handle FROM clause subquery, here.
            return false;
        }
        Table table = ((StmtTargetTableScan) tableScan).getTargetTable();
        // This table's scans need to be proven deterministic.
        allScansAreDeterministic = false;
        // Search indexes for one that makes the order by deterministic
        for (Index index : table.getIndexes()) {
            // skip non-unique indexes
            if (!index.getUnique()) {
                continue;
            }
            // get the list of expressions for the index
            List<AbstractExpression> indexExpressions = new ArrayList<>();
            String jsonExpr = index.getExpressionsjson();
            // if this is a pure-column index...
            if (jsonExpr.isEmpty()) {
                for (ColumnRef cref : index.getColumns()) {
                    Column col = cref.getColumn();
                    TupleValueExpression tve = new TupleValueExpression(table.getTypeName(), orderedAlias.getKey(), col.getName(), col.getName(), col.getIndex());
                    indexExpressions.add(tve);
                }
            } else // if this is a fancy expression-based index...
            {
                try {
                    indexExpressions = AbstractExpression.fromJSONArrayString(jsonExpr, tableScan);
                } catch (JSONException e) {
                    e.printStackTrace();
                    assert (false);
                    continue;
                }
            }
            //    ORDER BY A.unique_id, A.b_id
            if (orderedAliasExprs.containsAll(indexExpressions)) {
                allScansAreDeterministic = true;
                break;
            }
        }
        // ALL tables' scans need to have proved deterministic
        if (!allScansAreDeterministic) {
            return false;
        }
    }
    return true;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) TupleValueExpression(org.voltdb.expressions.TupleValueExpression) Table(org.voltdb.catalog.Table) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) JSONException(org.json_voltpatches.JSONException) Index(org.voltdb.catalog.Index) StmtTableScan(org.voltdb.planner.parseinfo.StmtTableScan) 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) ColumnRef(org.voltdb.catalog.ColumnRef) HashSet(java.util.HashSet)

Example 10 with StmtSubqueryScan

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

the class AbstractParsedStmt method addSubqueryToStmtCache.

/**
     * Add a sub-query to the statement cache.
     * @param subquery
     * @param tableAlias
     * @return the cache entry
     */
protected StmtSubqueryScan addSubqueryToStmtCache(AbstractParsedStmt subquery, String tableAlias) {
    assert (subquery != null);
    // generate a unique one for internal use.
    if (tableAlias == null) {
        tableAlias = AbstractParsedStmt.TEMP_TABLE_NAME + "_" + subquery.m_stmtId;
    }
    StmtSubqueryScan subqueryScan = new StmtSubqueryScan(subquery, tableAlias, m_stmtId);
    StmtTableScan prior = m_tableAliasMap.put(tableAlias, subqueryScan);
    assert (prior == null);
    return subqueryScan;
}
Also used : StmtSubqueryScan(org.voltdb.planner.parseinfo.StmtSubqueryScan) StmtTableScan(org.voltdb.planner.parseinfo.StmtTableScan)

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