@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;
  }
  @Override
  public Void visitDistinctOperator(DistinctOperator op, IOptimizationContext ctx)
      throws AlgebricksException {
    ILogicalOperator op0 = op.getInputs().get(0).getOperator();
    List<FunctionalDependency> functionalDependencies = new ArrayList<FunctionalDependency>();
    ctx.putFDList(op, functionalDependencies);
    for (FunctionalDependency inherited : getOrComputeFDs(op0, ctx)) {
      boolean isCoveredByDistinctByVars = true;
      for (LogicalVariable v : inherited.getHead()) {
        if (!op.isDistinctByVar(v)) {
          isCoveredByDistinctByVars = false;
        }
      }
      if (isCoveredByDistinctByVars) {
        List<LogicalVariable> newTail = new ArrayList<LogicalVariable>();
        for (LogicalVariable v2 : inherited.getTail()) {
          if (op.isDistinctByVar(v2)) {
            newTail.add(v2);
          }
        }
        if (!newTail.isEmpty()) {
          List<LogicalVariable> newHead = new ArrayList<LogicalVariable>(inherited.getHead());
          FunctionalDependency newFd = new FunctionalDependency(newHead, newTail);
          functionalDependencies.add(newFd);
        }
      }
    }
    Set<LogicalVariable> gbySet = new HashSet<LogicalVariable>();
    List<LogicalExpressionReference> expressions = op.getExpressions();
    for (LogicalExpressionReference pRef : expressions) {
      ILogicalExpression p = pRef.getExpression();
      if (p.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
        VariableReferenceExpression v = (VariableReferenceExpression) p;
        gbySet.add(v.getVariableReference());
      }
    }
    LocalGroupingProperty lgp = new LocalGroupingProperty(gbySet);

    Map<LogicalVariable, EquivalenceClass> equivalenceClasses = getOrComputeEqClasses(op0, ctx);
    ctx.putEquivalenceClassMap(op, equivalenceClasses);

    lgp.normalizeGroupingColumns(equivalenceClasses, functionalDependencies);
    Set<LogicalVariable> normSet = lgp.getColumnSet();
    List<LogicalExpressionReference> newDistinctByList =
        new ArrayList<LogicalExpressionReference>();
    for (LogicalExpressionReference p2Ref : expressions) {
      ILogicalExpression p2 = p2Ref.getExpression();
      if (p2.getExpressionTag() == LogicalExpressionTag.VARIABLE) {
        VariableReferenceExpression var2 = (VariableReferenceExpression) p2;
        LogicalVariable v2 = var2.getVariableReference();
        if (normSet.contains(v2)) {
          newDistinctByList.add(p2Ref);
        }
      } else {
        newDistinctByList.add(p2Ref);
      }
    }
    expressions.clear();
    expressions.addAll(newDistinctByList);
    return null;
  }