private boolean hasEqualityConstraints(int startPos, int endPos) {
   ScanRanges ranges = context.getScanRanges();
   // If a GROUP BY is being done, then the rows are ordered according to the GROUP BY key,
   // not by the original row key order of the table (see PHOENIX-3451).
   // We check each GROUP BY expression to see if it only references columns that are
   // matched by equality constraints, in which case the expression itself would be constant.
   // FIXME: this only recognizes row key columns that are held constant, not all columns.
   // FIXME: we should optimize out any GROUP BY or ORDER BY expression which is deemed to
   // be held constant based on the WHERE clause.
   if (!groupBy.isEmpty()) {
     for (int pos = startPos; pos < endPos; pos++) {
       IsConstantVisitor visitor = new IsConstantVisitor(this.projector, ranges);
       List<Expression> groupByExpressions = groupBy.getExpressions();
       if (pos >= groupByExpressions.size()) { // sanity check - shouldn't be necessary
         return false;
       }
       Expression groupByExpression = groupByExpressions.get(pos);
       if (groupByExpression.getDeterminism().ordinal() > Determinism.PER_STATEMENT.ordinal()) {
         return false;
       }
       Boolean isConstant = groupByExpression.accept(visitor);
       if (!Boolean.TRUE.equals(isConstant)) {
         return false;
       }
     }
     return true;
   }
   for (int pos = startPos; pos < endPos; pos++) {
     if (!ranges.hasEqualityConstraint(pos)) {
       return false;
     }
   }
   return true;
 }
 public OrderPreservingTracker(
     StatementContext context,
     GroupBy groupBy,
     Ordering ordering,
     int nNodes,
     TupleProjector projector) {
   this.context = context;
   if (groupBy.isEmpty()) {
     PTable table = context.getResolver().getTables().get(0).getTable();
     this.isOrderPreserving = table.rowKeyOrderOptimizable();
     boolean isSalted = table.getBucketNum() != null;
     boolean isMultiTenant =
         context.getConnection().getTenantId() != null && table.isMultiTenant();
     boolean isSharedViewIndex = table.getViewIndexId() != null;
     // TODO: util for this offset, as it's computed in numerous places
     this.pkPositionOffset =
         (isSalted ? 1 : 0) + (isMultiTenant ? 1 : 0) + (isSharedViewIndex ? 1 : 0);
   } else {
     this.isOrderPreserving = true;
     this.pkPositionOffset = 0;
   }
   this.groupBy = groupBy;
   this.visitor = new TrackOrderPreservingExpressionVisitor(projector);
   this.orderPreservingInfos = Lists.newArrayListWithExpectedSize(nNodes);
   this.ordering = ordering;
   this.projector = projector;
 }
  protected void explain(String prefix, List<String> planSteps) {
    StringBuilder buf = new StringBuilder(prefix);
    ScanRanges scanRanges = context.getScanRanges();
    boolean hasSkipScanFilter = false;
    if (scanRanges.isEverything()) {
      buf.append("FULL SCAN ");
    } else {
      hasSkipScanFilter = explainSkipScan(buf);
    }
    buf.append("OVER " + tableRef.getTable().getName().getString());
    if (!scanRanges.isPointLookup()) {
      appendKeyRanges(buf);
    }
    planSteps.add(buf.toString());

    Scan scan = context.getScan();
    Filter filter = scan.getFilter();
    PageFilter pageFilter = null;
    if (filter != null) {
      int offset = 0;
      boolean hasFirstKeyOnlyFilter = false;
      String filterDesc = "";
      if (hasSkipScanFilter) {
        if (filter instanceof FilterList) {
          List<Filter> filterList = ((FilterList) filter).getFilters();
          if (filterList.get(0) instanceof FirstKeyOnlyFilter) {
            hasFirstKeyOnlyFilter = true;
            offset = 1;
          }
          if (filterList.size() > offset + 1) {
            filterDesc = filterList.get(offset + 1).toString();
            if (filterList.size() > offset + 2) {
              pageFilter = (PageFilter) filterList.get(offset + 2);
            }
          }
        }
      } else if (filter instanceof FilterList) {
        List<Filter> filterList = ((FilterList) filter).getFilters();
        if (filterList.get(0) instanceof FirstKeyOnlyFilter) {
          hasFirstKeyOnlyFilter = true;
          offset = 1;
        }
        if (filterList.size() > offset) {
          filterDesc = filterList.get(offset).toString();
          if (filterList.size() > offset + 1) {
            pageFilter = (PageFilter) filterList.get(offset + 1);
          }
        }
      } else {
        if (filter instanceof FirstKeyOnlyFilter) {
          hasFirstKeyOnlyFilter = true;
        } else {
          filterDesc = filter.toString();
        }
      }
      if (filterDesc.length() > 0) {
        planSteps.add(
            "    SERVER FILTER BY "
                + (hasFirstKeyOnlyFilter ? "FIRST KEY ONLY AND " : "")
                + filterDesc);
      } else if (hasFirstKeyOnlyFilter) {
        planSteps.add("    SERVER FILTER BY FIRST KEY ONLY");
      }
      if (pageFilter != null) {
        planSteps.add("    SERVER " + pageFilter.getPageSize() + " ROW LIMIT");
      }
    }
    groupBy.explain(planSteps);
  }