private LogicalVariable getNewDecorVar(GroupByOperator g, LogicalVariable v) {
   for (Pair<LogicalVariable, LogicalExpressionReference> p : g.getDecorList()) {
     ILogicalExpression e = p.second.getExpression();
     if (e.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
       LogicalVariable v2 = ((VariableReferenceExpression) e).getVariableReference();
       if (v2 == v) {
         return (p.first != null) ? p.first : v2;
       }
     }
   }
   return null;
 }
 @Override
 public Void visitNestedTupleSourceOperator(NestedTupleSourceOperator op, IOptimizationContext ctx)
     throws AlgebricksException {
   AbstractLogicalOperator op1 =
       (AbstractLogicalOperator) op.getDataSourceReference().getOperator();
   ILogicalOperator inp1 = op1.getInputs().get(0).getOperator();
   Map<LogicalVariable, EquivalenceClass> eqClasses = getOrComputeEqClasses(inp1, ctx);
   ctx.putEquivalenceClassMap(op, eqClasses);
   List<FunctionalDependency> fds =
       new ArrayList<FunctionalDependency>(getOrComputeFDs(inp1, ctx));
   if (op1.getOperatorTag() == LogicalOperatorTag.GROUP) {
     GroupByOperator gby = (GroupByOperator) op1;
     LinkedList<LogicalVariable> tail = new LinkedList<LogicalVariable>();
     for (LogicalVariable v : gby.getGbyVarList()) {
       tail.add(v);
       // all values for gby vars. are the same
     }
     FunctionalDependency gbyfd =
         new FunctionalDependency(new LinkedList<LogicalVariable>(), tail);
     fds.add(gbyfd);
   }
   ctx.putFDList(op, fds);
   return null;
 }
  @Override
  public Void visitGroupByOperator(GroupByOperator op, IOptimizationContext ctx)
      throws AlgebricksException {
    Map<LogicalVariable, EquivalenceClass> equivalenceClasses =
        new HashMap<LogicalVariable, EquivalenceClass>();
    List<FunctionalDependency> functionalDependencies = new ArrayList<FunctionalDependency>();
    ctx.putEquivalenceClassMap(op, equivalenceClasses);
    ctx.putFDList(op, functionalDependencies);

    List<FunctionalDependency> inheritedFDs = new ArrayList<FunctionalDependency>();
    for (ILogicalPlan p : op.getNestedPlans()) {
      for (LogicalOperatorReference r : p.getRoots()) {
        ILogicalOperator op2 = r.getOperator();
        equivalenceClasses.putAll(getOrComputeEqClasses(op2, ctx));
        inheritedFDs.addAll(getOrComputeFDs(op2, ctx));
      }
    }

    ILogicalOperator op0 = op.getInputs().get(0).getOperator();
    inheritedFDs.addAll(getOrComputeFDs(op0, ctx));
    Map<LogicalVariable, EquivalenceClass> inheritedEcs = getOrComputeEqClasses(op0, ctx);
    for (FunctionalDependency inherited : inheritedFDs) {
      boolean isCoveredByGbyOrDecorVars = true;
      List<LogicalVariable> newHead = new ArrayList<LogicalVariable>(inherited.getHead().size());
      for (LogicalVariable v : inherited.getHead()) {
        LogicalVariable vnew = getNewGbyVar(op, v);
        if (vnew == null) {
          vnew = getNewDecorVar(op, v);
          if (vnew == null) {
            isCoveredByGbyOrDecorVars = false;
          }
          break;
        }
        newHead.add(vnew);
      }

      if (isCoveredByGbyOrDecorVars) {
        List<LogicalVariable> newTail = new ArrayList<LogicalVariable>();
        for (LogicalVariable v2 : inherited.getTail()) {
          LogicalVariable v3 = getNewGbyVar(op, v2);
          if (v3 != null) {
            newTail.add(v3);
          }
        }
        if (!newTail.isEmpty()) {
          FunctionalDependency newFd = new FunctionalDependency(newHead, newTail);
          functionalDependencies.add(newFd);
        }
      }
    }

    List<LogicalVariable> premiseGby = new LinkedList<LogicalVariable>();
    List<Pair<LogicalVariable, LogicalExpressionReference>> gByList = op.getGroupByList();
    for (Pair<LogicalVariable, LogicalExpressionReference> p : gByList) {
      premiseGby.add(p.first);
    }

    List<Pair<LogicalVariable, LogicalExpressionReference>> decorList = op.getDecorList();

    LinkedList<LogicalVariable> conclDecor = new LinkedList<LogicalVariable>();
    for (Pair<LogicalVariable, LogicalExpressionReference> p : decorList) {
      conclDecor.add(GroupByOperator.getDecorVariable(p));
    }
    if (!conclDecor.isEmpty()) {
      functionalDependencies.add(new FunctionalDependency(premiseGby, conclDecor));
    }

    Set<LogicalVariable> gbySet = new HashSet<LogicalVariable>();
    for (Pair<LogicalVariable, LogicalExpressionReference> p : gByList) {
      ILogicalExpression expr = p.second.getExpression();
      if (expr.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
        VariableReferenceExpression v = (VariableReferenceExpression) expr;
        gbySet.add(v.getVariableReference());
      }
    }
    LocalGroupingProperty lgp = new LocalGroupingProperty(gbySet);
    lgp.normalizeGroupingColumns(inheritedEcs, inheritedFDs);
    Set<LogicalVariable> normSet = lgp.getColumnSet();
    List<Pair<LogicalVariable, LogicalExpressionReference>> newGbyList =
        new ArrayList<Pair<LogicalVariable, LogicalExpressionReference>>();
    boolean changed = false;
    for (Pair<LogicalVariable, LogicalExpressionReference> p : gByList) {
      ILogicalExpression expr = p.second.getExpression();
      if (expr.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
        VariableReferenceExpression varRef = (VariableReferenceExpression) expr;
        LogicalVariable v2 = varRef.getVariableReference();
        EquivalenceClass ec2 = inheritedEcs.get(v2);
        LogicalVariable v3;
        if (ec2 != null && !ec2.representativeIsConst()) {
          v3 = ec2.getVariableRepresentative();
        } else {
          v3 = v2;
        }
        if (normSet.contains(v3)) {
          newGbyList.add(p);
        } else {
          changed = true;
          decorList.add(p);
        }
      } else {
        newGbyList.add(p);
      }
    }
    if (changed) {
      AlgebricksConfig.ALGEBRICKS_LOGGER.fine(
          ">>>> Group-by list changed from "
              + GroupByOperator.veListToString(gByList)
              + " to "
              + GroupByOperator.veListToString(newGbyList)
              + ".\n");
    }
    gByList.clear();
    gByList.addAll(newGbyList);
    return null;
  }