private void propagateFDsAndEquivClassesForUsedVars(
      ILogicalOperator op, IOptimizationContext ctx, List<LogicalVariable> usedVariables)
      throws AlgebricksException {
    ILogicalOperator op2 = op.getInputs().get(0).getOperator();
    Map<LogicalVariable, EquivalenceClass> eqClasses =
        new HashMap<LogicalVariable, EquivalenceClass>();
    ctx.putEquivalenceClassMap(op, eqClasses);
    List<FunctionalDependency> fds = new ArrayList<FunctionalDependency>();
    ctx.putFDList(op, fds);

    Map<LogicalVariable, EquivalenceClass> chldClasses = getOrComputeEqClasses(op2, ctx);
    for (LogicalVariable v : usedVariables) {
      EquivalenceClass ec = eqClasses.get(v);
      if (ec == null) {
        EquivalenceClass oc = chldClasses.get(v);
        if (oc == null) {
          continue;
        }
        List<LogicalVariable> m = new LinkedList<LogicalVariable>();
        for (LogicalVariable v2 : oc.getMembers()) {
          if (usedVariables.contains(v2)) {
            m.add(v2);
          }
        }
        EquivalenceClass nc;
        if (oc.representativeIsConst()) {
          nc = new EquivalenceClass(m, oc.getConstRepresentative());
        } else if (m.contains(oc.getVariableRepresentative())) {
          nc = new EquivalenceClass(m, oc.getVariableRepresentative());
        } else {
          nc = new EquivalenceClass(m, v);
        }
        for (LogicalVariable v3 : m) {
          eqClasses.put(v3, nc);
        }
      }
    }

    List<FunctionalDependency> chldFds = getOrComputeFDs(op2, ctx);
    for (FunctionalDependency fd : chldFds) {
      if (!usedVariables.containsAll(fd.getHead())) {
        continue;
      }
      List<LogicalVariable> tl = new LinkedList<LogicalVariable>();
      for (LogicalVariable v : fd.getTail()) {
        if (usedVariables.contains(v)) {
          tl.add(v);
        }
      }
      if (!tl.isEmpty()) {
        FunctionalDependency newFd = new FunctionalDependency(fd.getHead(), tl);
        fds.add(newFd);
      }
    }
  }
  @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;
  }