private void liftSparql11Subquery(
      final AST2BOpContext context, final StaticAnalysis sa, final SubqueryRoot subqueryRoot) {

    final IGroupNode<?> parent = subqueryRoot.getParent();

    final String newName = "-subSelect-" + context.nextId();

    final NamedSubqueryInclude include = new NamedSubqueryInclude(newName);

    /**
     * Set query hints from the parent join group.
     *
     * @see <a href="http://sourceforge.net/apps/trac/bigdata/ticket/791" > Clean up query hints
     *     </a>
     */
    include.setQueryHints((Properties) parent.getProperty(ASTBase.Annotations.QUERY_HINTS));

    /**
     * Copy across attached join filters.
     *
     * @see <a href="https://sourceforge.net/apps/trac/bigdata/ticket/796" >Filter assigned to
     *     sub-query by query generator is dropped from evaluation</a>
     */
    include.setAttachedJoinFilters(subqueryRoot.getAttachedJoinFilters());

    /*
     * Note: A SubqueryRoot normally starts out as the sole child of a
     * JoinGroupNode. However, other rewrites may have written out that
     * JoinGroupNode and it does not appear to be present for an ASK
     * subquery.
     *
     * Therefore, when the parent of the SubqueryRoot is a JoinGroupNode
     * having the SubqueryRoot as its only child, we use the parent's parent
     * in order to replace the JoinGroupNode when we lift out the
     * SubqueryRoot. Otherwise we use the parent since there is no wrapping
     * JoinGroupNode (or if there is, it has some other stuff in there as
     * well).
     *
     * BLZG-1542 -> there is an additional thing we need to take care of:
     *              whenever the parent node is an OPTIONAL or MINUS, we
     *              must not remove it, otherwise we would just "drop" an
     *              OPTIONAL or MINUS, thus changing the query's semantics
     *
     */

    if ((parent instanceof JoinGroupNode)
        && !((JoinGroupNode) parent).isOptional()
        && !((JoinGroupNode) parent).isMinus()
        && ((BOp) parent).arity() == 1
        && parent.getParent() != null
        && !((IGroupNode<?>) parent.getParent() instanceof UnionNode)) {

      final IGroupNode<IGroupMemberNode> pp = parent.getParent();

      // Replace the sub-select with the include.
      if (((ASTBase) pp).replaceWith((BOp) parent, include) == 0) throw new AssertionError();

    } else {

      // Replace the sub-select with the include.
      if (((ASTBase) parent).replaceWith((BOp) subqueryRoot, include) == 0)
        throw new AssertionError();
    }

    final NamedSubqueryRoot nsr = new NamedSubqueryRoot(subqueryRoot.getQueryType(), newName);

    /**
     * Copy across query hints from the original subquery.
     *
     * @see <a href="http://sourceforge.net/apps/trac/bigdata/ticket/791" > Clean up query hints
     *     </a>
     */
    nsr.setQueryHints(subqueryRoot.getQueryHints());

    nsr.setConstruct(subqueryRoot.getConstruct());
    nsr.setGroupBy(subqueryRoot.getGroupBy());
    nsr.setHaving(subqueryRoot.getHaving());
    nsr.setOrderBy(subqueryRoot.getOrderBy());
    nsr.setProjection(subqueryRoot.getProjection());
    nsr.setSlice(subqueryRoot.getSlice());
    nsr.setWhereClause(subqueryRoot.getWhereClause());
    nsr.setBindingsClause(subqueryRoot.getBindingsClause());

    sa.getQueryRoot().getNamedSubqueriesNotNull().add(nsr);
  }
  /** Apply all optimizations. */
  private void liftSubqueries(
      final AST2BOpContext context,
      final StaticAnalysis sa,
      final GraphPatternGroup<IGroupMemberNode> group) {

    final int arity = group.arity();

    for (int i = 0; i < arity; i++) {

      final BOp child = (BOp) group.get(i);

      if (child instanceof GraphPatternGroup<?>) {

        /*
         * Note: Do recursion *before* we do the rewrite so we will
         * rewrite Sub-Sub-Selects.
         *
         * FIXME Unit test for sub-sub-select optimization.
         */
        liftSubqueries(context, sa, ((GraphPatternGroup<IGroupMemberNode>) child));

      } else if (child instanceof SubqueryRoot) {

        // Recursion into subqueries.

        final SubqueryRoot subqueryRoot = (SubqueryRoot) child;

        liftSubqueries(context, sa, subqueryRoot.getWhereClause());

      } else if (child instanceof ServiceNode) {

        // Do not rewrite things inside of a SERVICE node.
        continue;
      }

      if (!(child instanceof SubqueryRoot)) {
        continue;
      }

      final SubqueryRoot subqueryRoot = (SubqueryRoot) child;

      if (subqueryRoot.getQueryType() == QueryType.ASK) {

        /*
         * FIXME Look at what would be involved in lifting an ASK
         * sub-query. There are going to be at least two cases. If there
         * is no join variable, then we always want to lift the ASK
         * sub-query as it is completely independent of the parent
         * group. If there is a join variable, then we need to project
         * solutions which include the join variables from the subquery
         * and the "ASK". At that point we can hash join against the
         * projected solutions and the ASK succeeds if the hash join
         * succeeds. [Add unit tests for this too.]
         */

        continue;
      }

      if (subqueryRoot.hasSlice()) {

        /*
         * Lift out SPARQL 1.1 subqueries which use LIMIT and/or OFFSET.
         *
         * The SliceOp in the subquery will cause the IRunningQuery in
         * which it appears to be interrupted. Therefore, when a SLICE
         * is required for a subquery we need to lift it out to run it
         * as a named subquery.
         *
         * TODO There may well be other cases that we have to handle
         * with as-bound evaluation of a Subquery with a LIMIT/OFFSET.
         * If so, then the subquery will have to be run using the
         * SubqueryOp.
         */

        liftSparql11Subquery(context, sa, subqueryRoot);

        continue;
      }
      if (subqueryRoot.hasSlice() && subqueryRoot.getOrderBy() != null) {

        /*
         * Lift out SPARQL 1.1 subqueries which use both LIMIT and ORDER
         * BY. Due to the interaction of the LIMIT and ORDER BY clause,
         * these subqueries MUST be run first since they can produce
         * different results if they are run "as-bound".
         */

        liftSparql11Subquery(context, sa, subqueryRoot);

        continue;
      }

      if (StaticAnalysis.isAggregate(subqueryRoot)) {

        /*
         * Lift out SPARQL 1.1 subqueries which use {@link IAggregate}s.
         * This typically provides more efficient evaluation than
         * repeated as-bound evaluation of the sub-select. It also
         * prevents inappropriate sharing of the internal state of the
         * {@link IAggregate} functions.
         */

        liftSparql11Subquery(context, sa, subqueryRoot);

        continue;
      }

      if (subqueryRoot.isRunOnce()) {

        /*
         * Lift out SPARQL 1.1 subqueries for which the RUN_ONCE
         * annotation was specified.
         */

        liftSparql11Subquery(context, sa, subqueryRoot);

        continue;
      }

      /*
       * FIXME We can not correctly predict the join variables at this
       * time because that depends on the actual evaluation order. This
       * has been commented out for now because it will otherwise cause
       * all sub-selects to be lifted out.
       */
      if (false) {
        final Set<IVariable<?>> joinVars =
            sa.getJoinVars(subqueryRoot, new LinkedHashSet<IVariable<?>>());

        if (joinVars.isEmpty()) {

          /*
           * Lift out SPARQL 1.1 subqueries for which the RUN_ONCE
           * annotation was specified.
           */

          liftSparql11Subquery(context, sa, subqueryRoot);

          continue;
        }
      }
    }
  }