/**
   * This overload variant of optimizeStatement is used by subclass CursorNode (as well as a minion
   * for the no-arg variant).
   *
   * @param offset Any OFFSET row count, or null
   * @param fetchFirst Any FETCH FIRST row count or null
   * @exception StandardException Thrown on error
   * @see DMLStatementNode#optimizeStatement()
   */
  protected void optimizeStatement(ValueNode offset, ValueNode fetchFirst)
      throws StandardException {
    resultSet = resultSet.preprocess(getCompilerContext().getNumTables(), null, (FromList) null);
    resultSet = resultSet.optimize(getDataDictionary(), null, 1.0d);

    resultSet = resultSet.modifyAccessPaths();

    // Any OFFSET/FETCH FIRST narrowing must be done *after* any rewrite of
    // the query tree (if not, underlying GROUP BY fails), but *before* the
    // final scroll insensitive result node set is added - that one needs
    // to sit on top - so now is the time.
    //
    // This example statement fails if we wrap *before* the optimization
    // above:
    //     select max(a) from t1 group by b fetch first row only
    //
    // A java.sql.ResultSet#previous on a scrollable result set will fail
    // if we don't wrap *after* the ScrollInsensitiveResultSetNode below.
    //
    // We need only wrap the RowCountNode set if at least one of the
    // clauses is present.

    if (offset != null || fetchFirst != null) {
      resultSet = wrapRowCountNode(resultSet, offset, fetchFirst);
    }

    /* If this is a cursor, then we
     * need to generate a new ResultSetNode to enable the scrolling
     * on top of the tree before modifying the access paths.
     */
    if (this instanceof CursorNode) {
      ResultColumnList siRCList;
      ResultColumnList childRCList;
      ResultSetNode siChild = resultSet;

      /* We get a shallow copy of the ResultColumnList and its
       * ResultColumns.  (Copy maintains ResultColumn.expression for now.)
       */
      siRCList = resultSet.getResultColumns();
      childRCList = siRCList.copyListAndObjects();
      resultSet.setResultColumns(childRCList);

      /* Replace ResultColumn.expression with new VirtualColumnNodes
       * in the ScrollInsensitiveResultSetNode's ResultColumnList.  (VirtualColumnNodes include
       * pointers to source ResultSetNode, this, and source ResultColumn.)
       */
      siRCList.genVirtualColumnNodes(resultSet, childRCList);

      /* Finally, we create the new ScrollInsensitiveResultSetNode */
      resultSet =
          (ResultSetNode)
              getNodeFactory()
                  .getNode(
                      C_NodeTypes.SCROLL_INSENSITIVE_RESULT_SET_NODE,
                      resultSet,
                      siRCList,
                      null,
                      getContextManager());
      // Propagate the referenced table map if it's already been created
      if (siChild.getReferencedTableMap() != null) {
        resultSet.setReferencedTableMap((JBitSet) siChild.getReferencedTableMap().clone());
      }
    }
  }
  /**
   * Preprocess a ResultSetNode - this currently means: o Generating a referenced table map for each
   * ResultSetNode. o Putting the WHERE and HAVING clauses in conjunctive normal form (CNF). o
   * Converting the WHERE and HAVING clauses into PredicateLists and classifying them. o Ensuring
   * that a ProjectRestrictNode is generated on top of every FromBaseTable and generated in place of
   * every FromSubquery. o Pushing single table predicates down to the new ProjectRestrictNodes.
   *
   * @param numTables The number of tables in the DML Statement
   * @param gbl The group by list, if any
   * @param fromList The from list, if any
   * @return ResultSetNode at top of preprocessed tree.
   * @exception StandardException Thrown on error
   */
  public ResultSetNode preprocess(int numTables, GroupByList gbl, FromList fromList)
      throws StandardException {
    // Push the order by list down to the ResultSet
    if (orderByList != null) {
      // If we have more than 1 ORDERBY columns, we may be able to
      // remove duplicate columns, e.g., "ORDER BY 1, 1, 2".
      if (orderByList.size() > 1) {
        orderByList.removeDupColumns();
      }

      subquery.pushOrderByList(orderByList);
      orderByList = null;
    }

    subquery.pushOffsetFetchFirst(offset, fetchFirst, hasJDBClimitClause);

    /* We want to chop out the FromSubquery from the tree and replace it
     * with a ProjectRestrictNode.  One complication is that there may be
     * ColumnReferences above us which point to the FromSubquery's RCL.
     * What we want to return is a tree with a PRN with the
     * FromSubquery's RCL on top.  (In addition, we don't want to be
     * introducing any redundant ProjectRestrictNodes.)
     * Another complication is that we want to be able to only push
     * projections and restrictions down to this ProjectRestrict, but
     * we want to be able to push them through as well.
     * So, we:
     *		o call subquery.preprocess() which returns a tree with
     *		  a SelectNode or a RowResultSetNode on top.
     *		o If the FSqry is flattenable(), then we return (so that the
     *		  caller can then call flatten()), otherwise we:
     *		o generate a PRN, whose RCL is the FSqry's RCL, on top of the result.
     *		o create a referencedTableMap for the PRN which represents
     *		  the FSqry's tableNumber, since ColumnReferences in the outer
     *		  query block would be referring to that one.
     *		  (This will allow us to push restrictions down to the PRN.)
     */

    subquery = subquery.preprocess(numTables, gbl, fromList);

    /* Return if the FSqry is flattenable()
     * NOTE: We can't flatten a FromSubquery if there is a group by list
     * because the group by list must be ColumnReferences.  For:
     *	select c1 from v1 group by c1,
     *	where v1 is select 1 from t1
     * The expression under the last redundant ResultColumn is an IntConstantNode,
     * not a ColumnReference.
     * We also do not flatten a subquery if tableProperties is non-null,
     * as the user is specifying 1 or more properties for the derived table,
     * which could potentially be lost on the flattening.
     * RESOLVE - this is too restrictive.
     */
    if ((gbl == null || gbl.size() == 0)
        && tableProperties == null
        && subquery.flattenableInFromSubquery(fromList)) {
      /* Set our table map to the subquery's table map. */
      setReferencedTableMap(subquery.getReferencedTableMap());
      return this;
    }

    return extractSubquery(numTables);
  }