@Override
  public Void visitLeftOuterJoinOperator(LeftOuterJoinOperator 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);
    ILogicalOperator opLeft = op.getInputs().get(0).getOperator();
    ILogicalOperator opRight = op.getInputs().get(1).getOperator();
    functionalDependencies.addAll(getOrComputeFDs(opLeft, ctx));
    functionalDependencies.addAll(getOrComputeFDs(opRight, ctx));
    equivalenceClasses.putAll(getOrComputeEqClasses(opLeft, ctx));
    equivalenceClasses.putAll(getOrComputeEqClasses(opRight, ctx));

    Collection<LogicalVariable> leftSideVars;
    if (opLeft.getSchema() == null) {
      leftSideVars = new LinkedList<LogicalVariable>();
      VariableUtilities.getLiveVariables(opLeft, leftSideVars);
      // actually, not all produced vars. are visible (due to projection)
      // so using cached schema is better and faster
    } else {
      leftSideVars = opLeft.getSchema();
    }
    ILogicalExpression expr = op.getCondition().getExpression();
    expr.getConstraintsForOuterJoin(functionalDependencies, leftSideVars);
    return null;
  }
 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 visitSelectOperator(SelectOperator 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);
   ILogicalOperator op0 = op.getInputs().get(0).getOperator();
   functionalDependencies.addAll(getOrComputeFDs(op0, ctx));
   equivalenceClasses.putAll(getOrComputeEqClasses(op0, ctx));
   ILogicalExpression expr = op.getCondition().getExpression();
   expr.getConstraintsAndEquivClasses(functionalDependencies, equivalenceClasses);
   return null;
 }
  private void fdsEqClassesForAbstractUnnestOperator(
      AbstractUnnestOperator op, IOptimizationContext ctx) throws AlgebricksException {
    ILogicalOperator inp1 = op.getInputs().get(0).getOperator();
    Map<LogicalVariable, EquivalenceClass> eqClasses = getOrComputeEqClasses(inp1, ctx);
    ctx.putEquivalenceClassMap(op, eqClasses);
    List<FunctionalDependency> fds = getOrComputeFDs(inp1, ctx);
    ctx.putFDList(op, fds);

    ILogicalExpression expr = op.getExpressionRef().getExpression();
    if (expr.getExpressionTag() == LogicalExpressionTag.FUNCTION_CALL) {
      AbstractFunctionCallExpression afe = (AbstractFunctionCallExpression) expr;
      if (afe.getKind() == FunctionKind.UNNEST
          && ((UnnestingFunctionCallExpression) afe).returnsUniqueValues()) {
        List<LogicalVariable> vars = new ArrayList<LogicalVariable>();
        VariableUtilities.getLiveVariables(op, vars);
        ArrayList<LogicalVariable> h = new ArrayList<LogicalVariable>();
        h.addAll(op.getVariables());
        FunctionalDependency fd = new FunctionalDependency(h, vars);
        fds.add(fd);
      }
    }
  }
  @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;
  }