Added LeftOuterUnnestMap operator.

 - Added LeftOuterUnnestMap operator to represent the left-outer-join semantics properly.

Change-Id: I4525899cf8e5e43551aa2ac2a78806ef6cc85e58
Reviewed-on: https://asterix-gerrit.ics.uci.edu/638
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Yingyi Bu <buyingyi@gmail.com>
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/LogicalOperatorTag.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/LogicalOperatorTag.java
index 6361f42..cad24e1 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/LogicalOperatorTag.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/base/LogicalOperatorTag.java
@@ -33,6 +33,7 @@
     INNERJOIN,
     INSERT_DELETE_UPSERT,
     LEFTOUTERJOIN,
+    LEFT_OUTER_UNNEST_MAP,
     LIMIT,
     MATERIALIZE,
     NESTEDTUPLESOURCE,
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractUnnestMapOperator.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractUnnestMapOperator.java
new file mode 100644
index 0000000..8a2981d
--- /dev/null
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/AbstractUnnestMapOperator.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.algebricks.core.algebra.operators.logical;
+
+import java.util.List;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.properties.VariablePropagationPolicy;
+
+public abstract class AbstractUnnestMapOperator extends AbstractUnnestOperator {
+
+    protected final Mutable<ILogicalExpression> expression;
+    protected final List<Object> variableTypes;
+    protected boolean propagateInput;
+    protected List<Mutable<ILogicalExpression>> additionalFilteringExpressions;
+    protected List<LogicalVariable> minFilterVars;
+    protected List<LogicalVariable> maxFilterVars;
+
+    public AbstractUnnestMapOperator(List<LogicalVariable> variables, Mutable<ILogicalExpression> expression,
+            List<Object> variableTypes, boolean propagateInput) {
+        super(variables, expression);
+        this.expression = expression;
+        this.variableTypes = variableTypes;
+        this.propagateInput = propagateInput;
+    }
+
+    public List<Object> getVariableTypes() {
+        return variableTypes;
+    }
+
+    /**
+     * If propagateInput is true, then propagates the input variables.
+     */
+    @Override
+    public VariablePropagationPolicy getVariablePropagationPolicy() {
+        return new VariablePropagationPolicy() {
+            @Override
+            public void propagateVariables(IOperatorSchema target, IOperatorSchema... sources)
+                    throws AlgebricksException {
+                if (propagateInput) {
+                    target.addAllVariables(sources[0]);
+                }
+                for (LogicalVariable v : variables) {
+                    target.addVariable(v);
+                }
+            }
+        };
+    }
+
+    public boolean propagatesInput() {
+        return propagateInput;
+    }
+
+    public void setPropagatesInput(boolean propagateInput) {
+        this.propagateInput = propagateInput;
+    }
+
+    public List<LogicalVariable> getMinFilterVars() {
+        return minFilterVars;
+    }
+
+    public void setMinFilterVars(List<LogicalVariable> minFilterVars) {
+        this.minFilterVars = minFilterVars;
+    }
+
+    public List<LogicalVariable> getMaxFilterVars() {
+        return maxFilterVars;
+    }
+
+    public void setMaxFilterVars(List<LogicalVariable> maxFilterVars) {
+        this.maxFilterVars = maxFilterVars;
+    }
+
+    public void setAdditionalFilteringExpressions(List<Mutable<ILogicalExpression>> additionalFilteringExpressions) {
+        this.additionalFilteringExpressions = additionalFilteringExpressions;
+    }
+
+    public List<Mutable<ILogicalExpression>> getAdditionalFilteringExpressions() {
+        return additionalFilteringExpressions;
+    }
+
+}
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
new file mode 100644
index 0000000..56e2dfb
--- /dev/null
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/LeftOuterUnnestMapOperator.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.algebricks.core.algebra.operators.logical;
+
+import java.util.List;
+
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
+import org.apache.hyracks.algebricks.core.algebra.typing.ITypingContext;
+import org.apache.hyracks.algebricks.core.algebra.typing.PropagatingTypeEnvironment;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
+
+/**
+ * Left-outer-unnest-map is similar to the unnest-map operator. The only
+ * difference is that this operator represents left-outer semantics, meaning
+ * that it generates null values for non-matching tuples. It also propagates all
+ * input variables. This may be used only in a left-outer-join case.
+ */
+public class LeftOuterUnnestMapOperator extends AbstractUnnestMapOperator {
+
+    public LeftOuterUnnestMapOperator(List<LogicalVariable> variables, Mutable<ILogicalExpression> expression,
+            List<Object> variableTypes, boolean propagateInput) {
+        super(variables, expression, variableTypes, propagateInput);
+        // propagateInput is always set to true for this operator.
+        this.propagateInput = true;
+    }
+
+    @Override
+    public LogicalOperatorTag getOperatorTag() {
+        return LogicalOperatorTag.LEFT_OUTER_UNNEST_MAP;
+    }
+
+    @Override
+    public <R, T> R accept(ILogicalOperatorVisitor<R, T> visitor, T arg) throws AlgebricksException {
+        return visitor.visitLeftOuterUnnestMapOperator(this, arg);
+    }
+
+    @Override
+    public IVariableTypeEnvironment computeOutputTypeEnvironment(ITypingContext ctx) throws AlgebricksException {
+        // Propagates all input variables that come from the outer branch.
+        PropagatingTypeEnvironment env = createPropagatingAllInputsTypeEnvironment(ctx);
+
+        env.getCorrelatedNullableVariableLists().add(variables);
+
+        // For the variables from the inner branch, the output type is the union
+        // of (original type + null).
+        for (int i = 0; i < variables.size(); i++) {
+            env.setVarType(variables.get(i), ctx.getNullableTypeComputer().makeNullableType(variableTypes.get(i)));
+        }
+
+        return env;
+    }
+
+}
\ No newline at end of file
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnnestMapOperator.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnnestMapOperator.java
index ebcc4f2..89e2423 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnnestMapOperator.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/UnnestMapOperator.java
@@ -26,23 +26,15 @@
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalOperatorTag;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
-import org.apache.hyracks.algebricks.core.algebra.properties.VariablePropagationPolicy;
 import org.apache.hyracks.algebricks.core.algebra.typing.ITypingContext;
 import org.apache.hyracks.algebricks.core.algebra.typing.NonPropagatingTypeEnvironment;
 import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
 
-public class UnnestMapOperator extends AbstractUnnestOperator {
-    private final List<Object> variableTypes;
-    private boolean propagateInput;
-    private List<Mutable<ILogicalExpression>> additionalFilteringExpressions;
-    private List<LogicalVariable> minFilterVars;
-    private List<LogicalVariable> maxFilterVars;
+public class UnnestMapOperator extends AbstractUnnestMapOperator {
 
     public UnnestMapOperator(List<LogicalVariable> variables, Mutable<ILogicalExpression> expression,
             List<Object> variableTypes, boolean propagateInput) {
-        super(variables, expression);
-        this.variableTypes = variableTypes;
-        this.propagateInput = propagateInput;
+        super(variables, expression, variableTypes, propagateInput);
     }
 
     @Override
@@ -55,31 +47,8 @@
         return visitor.visitUnnestMapOperator(this, arg);
     }
 
-    /**
-     * UnnestMap doesn't propagate input variables, because currently it is only
-     * used to search indexes. In the future, it would be nice to have the
-     * choice to propagate input variables or not.
-     */
-    @Override
-    public VariablePropagationPolicy getVariablePropagationPolicy() {
-        return new VariablePropagationPolicy() {
-            @Override
-            public void propagateVariables(IOperatorSchema target, IOperatorSchema... sources)
-                    throws AlgebricksException {
-                if (propagateInput) {
-                    target.addAllVariables(sources[0]);
-                }
-                for (LogicalVariable v : variables) {
-                    target.addVariable(v);
-                }
-            }
-        };
-    }
-
-    public List<Object> getVariableTypes() {
-        return variableTypes;
-    }
-
+    // When propagateInput is true,
+    // this operator propagates all input variables.
     @Override
     public IVariableTypeEnvironment computeOutputTypeEnvironment(ITypingContext ctx) throws AlgebricksException {
         IVariableTypeEnvironment env = null;
@@ -95,40 +64,4 @@
         return env;
     }
 
-    public boolean propagatesInput() {
-        return propagateInput;
-    }
-
-    public void setPropagatesInput(boolean propagateInput) {
-        this.propagateInput = propagateInput;
-    }
-
-    public List<LogicalVariable> getMinFilterVars() {
-        return minFilterVars;
-    }
-
-    public void setMinFilterVars(List<LogicalVariable> minFilterVars) {
-        this.minFilterVars = minFilterVars;
-    }
-
-    public List<LogicalVariable> getMaxFilterVars() {
-        return maxFilterVars;
-    }
-
-    public void setMaxFilterVars(List<LogicalVariable> maxFilterVars) {
-        this.maxFilterVars = maxFilterVars;
-    }
-
-    public void setAdditionalFilteringExpressions(List<Mutable<ILogicalExpression>> additionalFilteringExpressions) {
-        this.additionalFilteringExpressions = additionalFilteringExpressions;
-    }
-
-    public List<Mutable<ILogicalExpression>> getAdditionalFilteringExpressions() {
-        return additionalFilteringExpressions;
-    }
-
-    @Override
-    public boolean isMap() {
-        return true;
-    }
 }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/CardinalityInferenceVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/CardinalityInferenceVisitor.java
index 7b304ae..e90a685 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/CardinalityInferenceVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/CardinalityInferenceVisitor.java
@@ -35,6 +35,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -57,11 +58,9 @@
 import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalOperatorVisitor;
 
 /**
- * A visitor that provides the basic inference of tuple cardinalities of an operator's
- * output.
- * There are only two cases:
- * 1. the cardinality is one in the worst case;
- * 2. the cardinality is some unknown value.
+ * A visitor that provides the basic inference of tuple cardinalities of an
+ * operator's output. There are only two cases: 1. the cardinality is one in the
+ * worst case; 2. the cardinality is some unknown value.
  */
 public class CardinalityInferenceVisitor implements ILogicalOperatorVisitor<Long, Void> {
     private static final Long ONE = 1L;
@@ -186,6 +185,11 @@
     }
 
     @Override
+    public Long visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg) throws AlgebricksException {
+        return UNKNOWN;
+    }
+
+    @Override
     public Long visitDataScanOperator(DataSourceScanOperator op, Void arg) throws AlgebricksException {
         return UNKNOWN;
     }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/FDsAndEquivClassesVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/FDsAndEquivClassesVisitor.java
index 1805289..9a6411b 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/FDsAndEquivClassesVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/FDsAndEquivClassesVisitor.java
@@ -60,6 +60,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -487,6 +488,28 @@
     }
 
     @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, IOptimizationContext ctx)
+            throws AlgebricksException {
+        // Unlike the unnest-map operator, we propagate all inputs since
+        // propagateInuput is always true.
+        Map<LogicalVariable, EquivalenceClass> equivalenceClasses = new HashMap<LogicalVariable, EquivalenceClass>();
+        List<FunctionalDependency> functionalDependencies = new ArrayList<FunctionalDependency>();
+        ctx.putEquivalenceClassMap(op, equivalenceClasses);
+        ctx.putFDList(op, functionalDependencies);
+        ILogicalOperator childOp = op.getInputs().get(0).getValue();
+        functionalDependencies.addAll(getOrComputeFDs(childOp, ctx));
+        equivalenceClasses.putAll(getOrComputeEqClasses(childOp, ctx));
+
+        // Like Left-Outer join case, we add functional dependencies.
+        List<LogicalVariable> leftSideVars = new ArrayList<LogicalVariable>();
+        List<LogicalVariable> producedVars = new ArrayList<LogicalVariable>();
+        VariableUtilities.getUsedVariables(op, leftSideVars);
+        VariableUtilities.getProducedVariables(op, leftSideVars);
+        functionalDependencies.add(new FunctionalDependency(leftSideVars, producedVars));
+        return null;
+    }
+
+    @Override
     public Void visitUnnestOperator(UnnestOperator op, IOptimizationContext ctx) throws AlgebricksException {
         fdsEqClassesForAbstractUnnestOperator(op, ctx);
         return null;
@@ -579,13 +602,14 @@
     }
 
     /***
-     * Propagated equivalent classes from the child to the current operator, based
-     * on the used variables of the current operator.
+     * Propagated equivalent classes from the child to the current operator,
+     * based on the used variables of the current operator.
      *
      * @param op
      *            , the current operator
      * @param ctx
-     *            , the optimization context which keeps track of all equivalent classes.
+     *            , the optimization context which keeps track of all equivalent
+     *            classes.
      * @param usedVariables
      *            , used variables.
      * @throws AlgebricksException
@@ -627,9 +651,12 @@
             }
         }
 
-        // Propagates equivalent classes that contain expressions that use the used variables.
-        // Note that for the case variable $v is not in the used variables but it is
-        // equivalent to field-access($t, i) and $t is a used variable, the equivalent
+        // Propagates equivalent classes that contain expressions that use the
+        // used variables.
+        // Note that for the case variable $v is not in the used variables but
+        // it is
+        // equivalent to field-access($t, i) and $t is a used variable, the
+        // equivalent
         // class should still be propagated (kept).
         Set<LogicalVariable> usedVarSet = new HashSet<LogicalVariable>(usedVariables);
         for (Entry<LogicalVariable, EquivalenceClass> entry : chldClasses.entrySet()) {
@@ -642,7 +669,8 @@
                 if (!exprUsedVars.isEmpty()) {
                     for (LogicalVariable v : ec.getMembers()) {
                         eqClasses.put(v, ec);
-                        // If variable members contain a used variable, the representative
+                        // If variable members contain a used variable, the
+                        // representative
                         // variable should be a used variable.
                         if (usedVarSet.contains(v)) {
                             ec.setVariableRepresentative(v);
@@ -676,8 +704,9 @@
         Map<LogicalVariable, EquivalenceClass> eqClasses = getOrCreateEqClasses(op, ctx);
         Map<LogicalVariable, EquivalenceClass> propagatedEqClasses = getOrComputeEqClasses(inp1, ctx);
         /**
-         * The original eq classes of unnest-map are only for produced variables, therefore
-         * eqClasses and propagatedEqClasses do not have overlaps.
+         * The original eq classes of unnest-map are only for produced
+         * variables, therefore eqClasses and propagatedEqClasses do not have
+         * overlaps.
          */
         eqClasses.putAll(propagatedEqClasses);
         ctx.putEquivalenceClassMap(op, eqClasses);
@@ -756,11 +785,14 @@
             Map<LogicalVariable, EquivalenceClass> newVarEqcMap = new HashMap<LogicalVariable, EquivalenceClass>();
             for (Entry<LogicalVariable, EquivalenceClass> entry : eqClasses.entrySet()) {
                 EquivalenceClass eqc = entry.getValue();
-                // If the equivalence class contains the right-hand-side expression,
-                // the left-hand-side variable is added into the equivalence class.
+                // If the equivalence class contains the right-hand-side
+                // expression,
+                // the left-hand-side variable is added into the equivalence
+                // class.
                 if (eqc.contains(expr)) {
                     eqc.addMember(var);
-                    newVarEqcMap.put(var, eqc); // Add var as a map key for the equivalence class.
+                    newVarEqcMap.put(var, eqc); // Add var as a map key for the
+                                                // equivalence class.
                 }
             }
             eqClasses.putAll(newVarEqcMap);
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
index f849a11..5ae116b 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismOperatorVisitor.java
@@ -48,6 +48,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -83,8 +84,9 @@
     @Override
     public Boolean visitAggregateOperator(AggregateOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.AGGREGATE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.AGGREGATE) {
             return Boolean.FALSE;
+        }
         AggregateOperator aggOpArg = (AggregateOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(
                 getPairList(op.getVariables(), op.getExpressions()),
@@ -96,8 +98,9 @@
     public Boolean visitRunningAggregateOperator(RunningAggregateOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.RUNNINGAGGREGATE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.RUNNINGAGGREGATE) {
             return Boolean.FALSE;
+        }
         RunningAggregateOperator aggOpArg = (RunningAggregateOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(
                 getPairList(op.getVariables(), op.getExpressions()),
@@ -109,16 +112,18 @@
     public Boolean visitEmptyTupleSourceOperator(EmptyTupleSourceOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) copyAndSubstituteVar(op, arg);
-        if (aop.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.EMPTYTUPLESOURCE) {
             return Boolean.FALSE;
+        }
         return Boolean.TRUE;
     }
 
     @Override
     public Boolean visitExtensionOperator(ExtensionOperator op, ILogicalOperator arg) throws AlgebricksException {
         ExtensionOperator aop = (ExtensionOperator) copyAndSubstituteVar(op, arg);
-        if (aop.getOperatorTag() != LogicalOperatorTag.EXTENSION_OPERATOR)
+        if (aop.getOperatorTag() != LogicalOperatorTag.EXTENSION_OPERATOR) {
             return Boolean.FALSE;
+        }
         return Boolean.TRUE;
     }
 
@@ -128,8 +133,9 @@
         // require the same physical operator, otherwise delivers different data
         // properties
         if (aop.getOperatorTag() != LogicalOperatorTag.GROUP
-                || aop.getPhysicalOperator().getOperatorTag() != op.getPhysicalOperator().getOperatorTag())
+                || aop.getPhysicalOperator().getOperatorTag() != op.getPhysicalOperator().getOperatorTag()) {
             return Boolean.FALSE;
+        }
 
         List<Pair<LogicalVariable, Mutable<ILogicalExpression>>> keyLists = op.getGroupByList();
         GroupByOperator gbyOpArg = (GroupByOperator) copyAndSubstituteVar(op, arg);
@@ -145,12 +151,14 @@
 
         boolean isomorphic = VariableUtilities.varListEqualUnordered(listLeft, listRight);
 
-        if (!isomorphic)
+        if (!isomorphic) {
             return Boolean.FALSE;
+        }
         int sizeOp = op.getNestedPlans().size();
         int sizeArg = gbyOpArg.getNestedPlans().size();
-        if (sizeOp != sizeArg)
+        if (sizeOp != sizeArg) {
             return Boolean.FALSE;
+        }
 
         GroupByOperator argOp = (GroupByOperator) arg;
         List<ILogicalPlan> plans = op.getNestedPlans();
@@ -158,14 +166,16 @@
         for (int i = 0; i < plans.size(); i++) {
             List<Mutable<ILogicalOperator>> roots = plans.get(i).getRoots();
             List<Mutable<ILogicalOperator>> rootsArg = plansArg.get(i).getRoots();
-            if (roots.size() != rootsArg.size())
+            if (roots.size() != rootsArg.size()) {
                 return Boolean.FALSE;
+            }
             for (int j = 0; j < roots.size(); j++) {
                 ILogicalOperator topOp1 = roots.get(j).getValue();
                 ILogicalOperator topOp2 = rootsArg.get(j).getValue();
                 isomorphic = IsomorphismUtilities.isOperatorIsomorphicPlanSegment(topOp1, topOp2);
-                if (!isomorphic)
+                if (!isomorphic) {
                     return Boolean.FALSE;
+                }
             }
         }
         return isomorphic;
@@ -174,11 +184,13 @@
     @Override
     public Boolean visitLimitOperator(LimitOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.LIMIT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.LIMIT) {
             return Boolean.FALSE;
+        }
         LimitOperator limitOpArg = (LimitOperator) copyAndSubstituteVar(op, arg);
-        if (op.getOffset() != limitOpArg.getOffset())
+        if (op.getOffset() != limitOpArg.getOffset()) {
             return Boolean.FALSE;
+        }
         boolean isomorphic = op.getMaxObjects().getValue().equals(limitOpArg.getMaxObjects().getValue());
         return isomorphic;
     }
@@ -186,8 +198,9 @@
     @Override
     public Boolean visitInnerJoinOperator(InnerJoinOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.INNERJOIN)
+        if (aop.getOperatorTag() != LogicalOperatorTag.INNERJOIN) {
             return Boolean.FALSE;
+        }
         InnerJoinOperator joinOpArg = (InnerJoinOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = op.getCondition().getValue().equals(joinOpArg.getCondition().getValue());
         return isomorphic;
@@ -197,8 +210,9 @@
     public Boolean visitLeftOuterJoinOperator(LeftOuterJoinOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.LEFTOUTERJOIN)
+        if (aop.getOperatorTag() != LogicalOperatorTag.LEFTOUTERJOIN) {
             return Boolean.FALSE;
+        }
         LeftOuterJoinOperator joinOpArg = (LeftOuterJoinOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = op.getCondition().getValue().equals(joinOpArg.getCondition().getValue());
         return isomorphic;
@@ -208,16 +222,18 @@
     public Boolean visitNestedTupleSourceOperator(NestedTupleSourceOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.NESTEDTUPLESOURCE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.NESTEDTUPLESOURCE) {
             return Boolean.FALSE;
+        }
         return Boolean.TRUE;
     }
 
     @Override
     public Boolean visitOrderOperator(OrderOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.ORDER)
+        if (aop.getOperatorTag() != LogicalOperatorTag.ORDER) {
             return Boolean.FALSE;
+        }
         OrderOperator orderOpArg = (OrderOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = compareIOrderAndExpressions(op.getOrderExpressions(), orderOpArg.getOrderExpressions());
         return isomorphic;
@@ -226,8 +242,9 @@
     @Override
     public Boolean visitAssignOperator(AssignOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.ASSIGN)
+        if (aop.getOperatorTag() != LogicalOperatorTag.ASSIGN) {
             return Boolean.FALSE;
+        }
         AssignOperator assignOpArg = (AssignOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(
                 getPairList(op.getVariables(), op.getExpressions()),
@@ -238,8 +255,9 @@
     @Override
     public Boolean visitSelectOperator(SelectOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.SELECT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.SELECT) {
             return Boolean.FALSE;
+        }
         SelectOperator selectOpArg = (SelectOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = op.getCondition().getValue().equals(selectOpArg.getCondition().getValue());
         return isomorphic;
@@ -248,8 +266,9 @@
     @Override
     public Boolean visitProjectOperator(ProjectOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.PROJECT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.PROJECT) {
             return Boolean.FALSE;
+        }
         ProjectOperator projectOpArg = (ProjectOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), projectOpArg.getVariables());
         return isomorphic;
@@ -259,8 +278,9 @@
     public Boolean visitPartitioningSplitOperator(PartitioningSplitOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.PARTITIONINGSPLIT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.PARTITIONINGSPLIT) {
             return Boolean.FALSE;
+        }
         PartitioningSplitOperator partitionOpArg = (PartitioningSplitOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = compareExpressions(op.getExpressions(), partitionOpArg.getExpressions());
         return isomorphic;
@@ -269,24 +289,27 @@
     @Override
     public Boolean visitReplicateOperator(ReplicateOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.REPLICATE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.REPLICATE) {
             return Boolean.FALSE;
+        }
         return Boolean.TRUE;
     }
 
     @Override
     public Boolean visitMaterializeOperator(MaterializeOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.MATERIALIZE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.MATERIALIZE) {
             return Boolean.FALSE;
+        }
         return Boolean.TRUE;
     }
 
     @Override
     public Boolean visitScriptOperator(ScriptOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.SCRIPT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.SCRIPT) {
             return Boolean.FALSE;
+        }
         ScriptOperator scriptOpArg = (ScriptOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = op.getScriptDescription().equals(scriptOpArg.getScriptDescription());
         return isomorphic;
@@ -295,22 +318,25 @@
     @Override
     public Boolean visitSubplanOperator(SubplanOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.SUBPLAN)
+        if (aop.getOperatorTag() != LogicalOperatorTag.SUBPLAN) {
             return Boolean.FALSE;
+        }
         SubplanOperator subplanOpArg = (SubplanOperator) copyAndSubstituteVar(op, arg);
         List<ILogicalPlan> plans = op.getNestedPlans();
         List<ILogicalPlan> plansArg = subplanOpArg.getNestedPlans();
         for (int i = 0; i < plans.size(); i++) {
             List<Mutable<ILogicalOperator>> roots = plans.get(i).getRoots();
             List<Mutable<ILogicalOperator>> rootsArg = plansArg.get(i).getRoots();
-            if (roots.size() == rootsArg.size())
+            if (roots.size() == rootsArg.size()) {
                 return Boolean.FALSE;
+            }
             for (int j = 0; j < roots.size(); j++) {
                 ILogicalOperator topOp1 = roots.get(j).getValue();
                 ILogicalOperator topOp2 = rootsArg.get(j).getValue();
                 boolean isomorphic = IsomorphismUtilities.isOperatorIsomorphicPlanSegment(topOp1, topOp2);
-                if (!isomorphic)
+                if (!isomorphic) {
                     return Boolean.FALSE;
+                }
             }
         }
         return Boolean.TRUE;
@@ -319,36 +345,38 @@
     @Override
     public Boolean visitUnionOperator(UnionAllOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.UNIONALL)
+        if (aop.getOperatorTag() != LogicalOperatorTag.UNIONALL) {
             return Boolean.FALSE;
+        }
         UnionAllOperator unionOpArg = (UnionAllOperator) copyAndSubstituteVar(op, arg);
         List<Triple<LogicalVariable, LogicalVariable, LogicalVariable>> mapping = op.getVariableMappings();
         List<Triple<LogicalVariable, LogicalVariable, LogicalVariable>> mappingArg = unionOpArg.getVariableMappings();
-        if (mapping.size() != mappingArg.size())
+        if (mapping.size() != mappingArg.size()) {
             return Boolean.FALSE;
+        }
         return VariableUtilities.varListEqualUnordered(mapping, mappingArg);
     }
 
     @Override
     public Boolean visitIntersectOperator(IntersectOperator op, ILogicalOperator arg) throws AlgebricksException {
-        if (op.getOperatorTag() != LogicalOperatorTag.INTERSECT){
+        if (op.getOperatorTag() != LogicalOperatorTag.INTERSECT) {
             return Boolean.FALSE;
         }
         IntersectOperator intersetOpArg = (IntersectOperator) copyAndSubstituteVar(op, arg);
         List<LogicalVariable> variables = op.getOutputVars();
         List<LogicalVariable> variablesArg = intersetOpArg.getOutputVars();
-        if (variables.size() != variablesArg.size()){
+        if (variables.size() != variablesArg.size()) {
             return Boolean.FALSE;
         }
-        if (!VariableUtilities.varListEqualUnordered(variables, variablesArg)){
+        if (!VariableUtilities.varListEqualUnordered(variables, variablesArg)) {
             return Boolean.FALSE;
         }
 
-        if (op.getNumInput() != intersetOpArg.getNumInput()){
+        if (op.getNumInput() != intersetOpArg.getNumInput()) {
             return Boolean.FALSE;
         }
-        for (int i = 0; i < op.getNumInput(); i++){
-            if (!VariableUtilities.varListEqualUnordered(op.getInputVariables(i), intersetOpArg.getInputVariables(i))){
+        for (int i = 0; i < op.getNumInput(); i++) {
+            if (!VariableUtilities.varListEqualUnordered(op.getInputVariables(i), intersetOpArg.getInputVariables(i))) {
                 return Boolean.FALSE;
             }
         }
@@ -358,13 +386,15 @@
     @Override
     public Boolean visitUnnestOperator(UnnestOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.UNNEST)
+        if (aop.getOperatorTag() != LogicalOperatorTag.UNNEST) {
             return Boolean.FALSE;
+        }
         UnnestOperator unnestOpArg = (UnnestOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), unnestOpArg.getVariables())
                 && variableEqual(op.getPositionalVariable(), unnestOpArg.getPositionalVariable());
-        if (!isomorphic)
+        if (!isomorphic) {
             return Boolean.FALSE;
+        }
         isomorphic = op.getExpressionRef().getValue().equals(unnestOpArg.getExpressionRef().getValue());
         return isomorphic;
     }
@@ -372,24 +402,44 @@
     @Override
     public Boolean visitUnnestMapOperator(UnnestMapOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.UNNEST_MAP)
+        if (aop.getOperatorTag() != LogicalOperatorTag.UNNEST_MAP) {
             return Boolean.FALSE;
+        }
         UnnestMapOperator unnestOpArg = (UnnestMapOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), unnestOpArg.getVariables());
-        if (!isomorphic)
+        if (!isomorphic) {
             return Boolean.FALSE;
+        }
         isomorphic = op.getExpressionRef().getValue().equals(unnestOpArg.getExpressionRef().getValue());
         return isomorphic;
     }
 
     @Override
+    public Boolean visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, ILogicalOperator arg)
+            throws AlgebricksException {
+        AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
+        if (aop.getOperatorTag() != LogicalOperatorTag.LEFT_OUTER_UNNEST_MAP) {
+            return Boolean.FALSE;
+        }
+        LeftOuterUnnestMapOperator loUnnestOpArg = (LeftOuterUnnestMapOperator) copyAndSubstituteVar(op, arg);
+        boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), loUnnestOpArg.getVariables());
+        if (!isomorphic) {
+            return Boolean.FALSE;
+        }
+        isomorphic = op.getExpressionRef().getValue().equals(loUnnestOpArg.getExpressionRef().getValue());
+        return isomorphic;
+    }
+
+    @Override
     public Boolean visitDataScanOperator(DataSourceScanOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.DATASOURCESCAN)
+        if (aop.getOperatorTag() != LogicalOperatorTag.DATASOURCESCAN) {
             return Boolean.FALSE;
+        }
         DataSourceScanOperator argScan = (DataSourceScanOperator) arg;
-        if (!argScan.getDataSource().toString().equals(op.getDataSource().toString()))
+        if (!argScan.getDataSource().toString().equals(op.getDataSource().toString())) {
             return Boolean.FALSE;
+        }
         DataSourceScanOperator scanOpArg = (DataSourceScanOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), scanOpArg.getVariables())
                 && op.getDataSource().toString().equals(scanOpArg.getDataSource().toString());
@@ -399,8 +449,9 @@
     @Override
     public Boolean visitDistinctOperator(DistinctOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.DISTINCT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.DISTINCT) {
             return Boolean.FALSE;
+        }
         DistinctOperator distinctOpArg = (DistinctOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = compareExpressions(op.getExpressions(), distinctOpArg.getExpressions());
         return isomorphic;
@@ -409,36 +460,44 @@
     @Override
     public Boolean visitExchangeOperator(ExchangeOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.EXCHANGE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.EXCHANGE) {
             return Boolean.FALSE;
+        }
         // require the same partition property
-        if (!(op.getPhysicalOperator().getOperatorTag() == aop.getPhysicalOperator().getOperatorTag()))
+        if (!(op.getPhysicalOperator().getOperatorTag() == aop.getPhysicalOperator().getOperatorTag())) {
             return Boolean.FALSE;
+        }
         variableMapping.clear();
         IsomorphismUtilities.mapVariablesTopDown(op, arg, variableMapping);
         IPhysicalPropertiesVector properties = op.getPhysicalOperator().getDeliveredProperties();
         IPhysicalPropertiesVector propertiesArg = aop.getPhysicalOperator().getDeliveredProperties();
-        if (properties == null && propertiesArg == null)
+        if (properties == null && propertiesArg == null) {
             return Boolean.TRUE;
-        if (properties == null || propertiesArg == null)
+        }
+        if (properties == null || propertiesArg == null) {
             return Boolean.FALSE;
+        }
         IPartitioningProperty partProp = properties.getPartitioningProperty();
         IPartitioningProperty partPropArg = propertiesArg.getPartitioningProperty();
-        if (!partProp.getPartitioningType().equals(partPropArg.getPartitioningType()))
+        if (!partProp.getPartitioningType().equals(partPropArg.getPartitioningType())) {
             return Boolean.FALSE;
+        }
         List<LogicalVariable> columns = new ArrayList<LogicalVariable>();
         partProp.getColumns(columns);
         List<LogicalVariable> columnsArg = new ArrayList<LogicalVariable>();
         partPropArg.getColumns(columnsArg);
-        if (columns.size() != columnsArg.size())
+        if (columns.size() != columnsArg.size()) {
             return Boolean.FALSE;
-        if (columns.size() == 0)
+        }
+        if (columns.size() == 0) {
             return Boolean.TRUE;
+        }
         for (int i = 0; i < columnsArg.size(); i++) {
             LogicalVariable rightVar = columnsArg.get(i);
             LogicalVariable leftVar = variableMapping.get(rightVar);
-            if (leftVar != null)
+            if (leftVar != null) {
                 columnsArg.set(i, leftVar);
+            }
         }
         return VariableUtilities.varListEqualUnordered(columns, columnsArg);
     }
@@ -446,8 +505,9 @@
     @Override
     public Boolean visitWriteOperator(WriteOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.WRITE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.WRITE) {
             return Boolean.FALSE;
+        }
         WriteOperator writeOpArg = (WriteOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), writeOpArg.getSchema());
         return isomorphic;
@@ -457,8 +517,9 @@
     public Boolean visitDistributeResultOperator(DistributeResultOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.DISTRIBUTE_RESULT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.DISTRIBUTE_RESULT) {
             return Boolean.FALSE;
+        }
         DistributeResultOperator writeOpArg = (DistributeResultOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), writeOpArg.getSchema());
         return isomorphic;
@@ -467,28 +528,35 @@
     @Override
     public Boolean visitWriteResultOperator(WriteResultOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.WRITE_RESULT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.WRITE_RESULT) {
             return Boolean.FALSE;
+        }
         WriteResultOperator writeOpArg = (WriteResultOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), writeOpArg.getSchema());
-        if (!op.getDataSource().equals(writeOpArg.getDataSource()))
+        if (!op.getDataSource().equals(writeOpArg.getDataSource())) {
             isomorphic = false;
-        if (!op.getPayloadExpression().equals(writeOpArg.getPayloadExpression()))
+        }
+        if (!op.getPayloadExpression().equals(writeOpArg.getPayloadExpression())) {
             isomorphic = false;
+        }
         return isomorphic;
     }
 
     @Override
-    public Boolean visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, ILogicalOperator arg) throws AlgebricksException {
+    public Boolean visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, ILogicalOperator arg)
+            throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.INSERT_DELETE_UPSERT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.INSERT_DELETE_UPSERT) {
             return Boolean.FALSE;
+        }
         InsertDeleteUpsertOperator insertOpArg = (InsertDeleteUpsertOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), insertOpArg.getSchema());
-        if (!op.getDataSource().equals(insertOpArg.getDataSource()))
+        if (!op.getDataSource().equals(insertOpArg.getDataSource())) {
             isomorphic = false;
-        if (!op.getPayloadExpression().equals(insertOpArg.getPayloadExpression()))
+        }
+        if (!op.getPayloadExpression().equals(insertOpArg.getPayloadExpression())) {
             isomorphic = false;
+        }
         return isomorphic;
     }
 
@@ -496,24 +564,28 @@
     public Boolean visitIndexInsertDeleteUpsertOperator(IndexInsertDeleteUpsertOperator op, ILogicalOperator arg)
             throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.INDEX_INSERT_DELETE_UPSERT)
+        if (aop.getOperatorTag() != LogicalOperatorTag.INDEX_INSERT_DELETE_UPSERT) {
             return Boolean.FALSE;
+        }
         IndexInsertDeleteUpsertOperator insertOpArg = (IndexInsertDeleteUpsertOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), insertOpArg.getSchema());
-        if (!op.getDataSourceIndex().equals(insertOpArg.getDataSourceIndex()))
+        if (!op.getDataSourceIndex().equals(insertOpArg.getDataSourceIndex())) {
             isomorphic = false;
+        }
         return isomorphic;
     }
 
     @Override
     public Boolean visitTokenizeOperator(TokenizeOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.TOKENIZE)
+        if (aop.getOperatorTag() != LogicalOperatorTag.TOKENIZE) {
             return Boolean.FALSE;
+        }
         TokenizeOperator tokenizeOpArg = (TokenizeOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getSchema(), tokenizeOpArg.getSchema());
-        if (!op.getDataSourceIndex().equals(tokenizeOpArg.getDataSourceIndex()))
+        if (!op.getDataSourceIndex().equals(tokenizeOpArg.getDataSourceIndex())) {
             isomorphic = false;
+        }
         return isomorphic;
     }
 
@@ -524,27 +596,32 @@
 
     private Boolean compareExpressions(List<Mutable<ILogicalExpression>> opExprs,
             List<Mutable<ILogicalExpression>> argExprs) {
-        if (opExprs.size() != argExprs.size())
+        if (opExprs.size() != argExprs.size()) {
             return Boolean.FALSE;
+        }
         for (int i = 0; i < opExprs.size(); i++) {
             boolean isomorphic = opExprs.get(i).getValue().equals(argExprs.get(i).getValue());
-            if (!isomorphic)
+            if (!isomorphic) {
                 return Boolean.FALSE;
+            }
         }
         return Boolean.TRUE;
     }
 
     private Boolean compareIOrderAndExpressions(List<Pair<IOrder, Mutable<ILogicalExpression>>> opOrderExprs,
             List<Pair<IOrder, Mutable<ILogicalExpression>>> argOrderExprs) {
-        if (opOrderExprs.size() != argOrderExprs.size())
+        if (opOrderExprs.size() != argOrderExprs.size()) {
             return Boolean.FALSE;
+        }
         for (int i = 0; i < opOrderExprs.size(); i++) {
             boolean isomorphic = opOrderExprs.get(i).first.equals(argOrderExprs.get(i).first);
-            if (!isomorphic)
+            if (!isomorphic) {
                 return Boolean.FALSE;
+            }
             isomorphic = opOrderExprs.get(i).second.getValue().equals(argOrderExprs.get(i).second.getValue());
-            if (!isomorphic)
+            if (!isomorphic) {
                 return Boolean.FALSE;
+            }
         }
         return Boolean.TRUE;
     }
@@ -556,16 +633,18 @@
         IsomorphismUtilities.mapVariablesTopDown(op, argOp, variableMapping);
 
         List<LogicalVariable> liveVars = new ArrayList<LogicalVariable>();
-        if (argOp.getInputs().size() > 0)
+        if (argOp.getInputs().size() > 0) {
             for (int i = 0; i < argOp.getInputs().size(); i++)
                 VariableUtilities.getLiveVariables(argOp.getInputs().get(i).getValue(), liveVars);
+        }
         List<LogicalVariable> producedVars = new ArrayList<LogicalVariable>();
         VariableUtilities.getProducedVariables(argOp, producedVars);
         List<LogicalVariable> producedVarsNew = new ArrayList<LogicalVariable>();
         VariableUtilities.getProducedVariables(op, producedVarsNew);
 
-        if (producedVars.size() != producedVarsNew.size())
+        if (producedVars.size() != producedVarsNew.size()) {
             return newOp;
+        }
         for (Entry<LogicalVariable, LogicalVariable> map : variableMapping.entrySet()) {
             if (liveVars.contains(map.getKey())) {
                 VariableUtilities.substituteVariables(newOp, map.getKey(), map.getValue(), null);
@@ -579,8 +658,9 @@
     public List<Pair<LogicalVariable, ILogicalExpression>> getPairList(List<LogicalVariable> vars,
             List<Mutable<ILogicalExpression>> exprs) throws AlgebricksException {
         List<Pair<LogicalVariable, ILogicalExpression>> list = new ArrayList<Pair<LogicalVariable, ILogicalExpression>>();
-        if (vars.size() != exprs.size())
+        if (vars.size() != exprs.size()) {
             throw new AlgebricksException("variable list size does not equal to expression list size ");
+        }
         for (int i = 0; i < vars.size(); i++) {
             list.add(new Pair<LogicalVariable, ILogicalExpression>(vars.get(i), exprs.get(i).getValue()));
         }
@@ -588,24 +668,28 @@
     }
 
     private static boolean variableEqual(LogicalVariable var, LogicalVariable varArg) {
-        if (var == null && varArg == null)
+        if (var == null && varArg == null) {
             return true;
-        if (var.equals(varArg))
+        }
+        if (var.equals(varArg)) {
             return true;
-        else
+        } else {
             return false;
+        }
     }
 
     @Override
     public Boolean visitOuterUnnestOperator(OuterUnnestOperator op, ILogicalOperator arg) throws AlgebricksException {
         AbstractLogicalOperator aop = (AbstractLogicalOperator) arg;
-        if (aop.getOperatorTag() != LogicalOperatorTag.OUTER_UNNEST)
+        if (aop.getOperatorTag() != LogicalOperatorTag.OUTER_UNNEST) {
             return Boolean.FALSE;
+        }
         OuterUnnestOperator unnestOpArg = (OuterUnnestOperator) copyAndSubstituteVar(op, arg);
         boolean isomorphic = VariableUtilities.varListEqualUnordered(op.getVariables(), unnestOpArg.getVariables())
                 && variableEqual(op.getPositionalVariable(), unnestOpArg.getPositionalVariable());
-        if (!isomorphic)
+        if (!isomorphic) {
             return Boolean.FALSE;
+        }
         isomorphic = op.getExpressionRef().getValue().equals(unnestOpArg.getExpressionRef().getValue());
         return isomorphic;
     }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismVariableMappingVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismVariableMappingVisitor.java
index b81f747..1b1b9d8 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismVariableMappingVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/IsomorphismVariableMappingVisitor.java
@@ -49,6 +49,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -220,6 +221,13 @@
     }
 
     @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, ILogicalOperator arg)
+            throws AlgebricksException {
+        mapVariablesStandard(op, arg);
+        return null;
+    }
+
+    @Override
     public Void visitDataScanOperator(DataSourceScanOperator op, ILogicalOperator arg) throws AlgebricksException {
         mapVariablesStandard(op, arg);
         return null;
@@ -257,7 +265,8 @@
     }
 
     @Override
-    public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, ILogicalOperator arg) throws AlgebricksException {
+    public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, ILogicalOperator arg)
+            throws AlgebricksException {
         mapVariablesStandard(op, arg);
         return null;
     }
@@ -435,12 +444,12 @@
 
     private void mapVariablesForIntersect(IntersectOperator op, ILogicalOperator arg) {
         IntersectOperator opArg = (IntersectOperator) arg;
-        if (op.getNumInput() != opArg.getNumInput()){
+        if (op.getNumInput() != opArg.getNumInput()) {
             return;
         }
-        for (int i = 0; i < op.getNumInput(); i++){
-            for (int j = 0; j < op.getInputVariables(i).size(); j++){
-                if (!varEquivalent(op.getInputVariables(i).get(j), opArg.getInputVariables(i).get(j))){
+        for (int i = 0; i < op.getNumInput(); i++) {
+            for (int j = 0; j < op.getInputVariables(i).size(); j++) {
+                if (!varEquivalent(op.getInputVariables(i).get(j), opArg.getInputVariables(i).get(j))) {
                     return;
                 }
             }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
index 3905d13..213b2b1 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalOperatorDeepCopyWithNewVariablesVisitor.java
@@ -46,6 +46,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -70,9 +71,9 @@
 import org.apache.hyracks.algebricks.core.algebra.visitors.IQueryOperatorVisitor;
 
 /**
- * This visitor deep-copies a query plan but uses a new set of variables.
- * Method getInputToOutputVariableMapping() will return a map that maps
- * input variables to their corresponding output variables.
+ * This visitor deep-copies a query plan but uses a new set of variables. Method
+ * getInputToOutputVariableMapping() will return a map that maps input variables
+ * to their corresponding output variables.
  */
 public class LogicalOperatorDeepCopyWithNewVariablesVisitor
         implements IQueryOperatorVisitor<ILogicalOperator, ILogicalOperator> {
@@ -84,12 +85,13 @@
     // original one in the copied plan.
     private final Map<LogicalVariable, LogicalVariable> inputVarToOutputVarMapping;
 
-    // Key: New variable in the new plan. Value: The old variable in the original plan.
+    // Key: New variable in the new plan. Value: The old variable in the
+    // original plan.
     private final Map<LogicalVariable, LogicalVariable> outputVarToInputVarMapping;
 
     /**
-     * @param IOptimizationContext,
-     *            the optimization context
+     * @param IOptimizationContext
+     *            , the optimization context
      */
     public LogicalOperatorDeepCopyWithNewVariablesVisitor(IVariableContext varContext, ITypingContext typeContext) {
         this.varContext = varContext;
@@ -478,7 +480,7 @@
             throws AlgebricksException {
         List<List<LogicalVariable>> liveVarsInInputs = getLiveVarsInInputs(op);
         List<LogicalVariable> outputCopy = new ArrayList<>();
-        for (LogicalVariable var : op.getOutputVars()){
+        for (LogicalVariable var : op.getOutputVars()) {
             outputCopy.add(deepCopyVariable(var));
         }
         IntersectOperator opCopy = new IntersectOperator(outputCopy, liveVarsInInputs);
@@ -511,6 +513,16 @@
     }
 
     @Override
+    public ILogicalOperator visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, ILogicalOperator arg)
+            throws AlgebricksException {
+        LeftOuterUnnestMapOperator opCopy = new LeftOuterUnnestMapOperator(deepCopyVariableList(op.getVariables()),
+                exprDeepCopyVisitor.deepCopyExpressionReference(op.getExpressionRef()), op.getVariableTypes(),
+                op.propagatesInput());
+        deepCopyInputsAnnotationsAndExecutionMode(op, arg, opCopy);
+        return opCopy;
+    }
+
+    @Override
     public ILogicalOperator visitUnnestOperator(UnnestOperator op, ILogicalOperator arg) throws AlgebricksException {
         UnnestOperator opCopy = new UnnestOperator(deepCopyVariable(op.getVariable()),
                 exprDeepCopyVisitor.deepCopyExpressionReference(op.getExpressionRef()),
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalPropertiesVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalPropertiesVisitor.java
index 2f3725d..7ef76ee 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalPropertiesVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/LogicalPropertiesVisitor.java
@@ -38,9 +38,10 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -240,6 +241,12 @@
     }
 
     @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, IOptimizationContext arg)
+            throws AlgebricksException {
+        return null;
+    }
+
+    @Override
     public Void visitUnnestOperator(UnnestOperator op, IOptimizationContext arg) throws AlgebricksException {
         // TODO Auto-generated method stub
         return null;
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
index 7fd3c46..931640e 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/OperatorDeepCopyVisitor.java
@@ -43,9 +43,10 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -209,7 +210,7 @@
     public ILogicalOperator visitIntersectOperator(IntersectOperator op, Void arg) throws AlgebricksException {
         List<LogicalVariable> outputVar = new ArrayList<>(op.getOutputVars());
         List<List<LogicalVariable>> inputVars = new ArrayList<>(op.getNumInput());
-        for(int i = 0; i < op.getNumInput(); i++){
+        for (int i = 0; i < op.getNumInput(); i++) {
             inputVars.add(new ArrayList<>(op.getInputVariables(i)));
         }
         return new IntersectOperator(outputVar, inputVars);
@@ -230,6 +231,15 @@
     }
 
     @Override
+    public ILogicalOperator visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg)
+            throws AlgebricksException {
+        ArrayList<LogicalVariable> newInputList = new ArrayList<LogicalVariable>();
+        newInputList.addAll(op.getVariables());
+        return new LeftOuterUnnestMapOperator(newInputList, deepCopyExpressionRef(op.getExpressionRef()),
+                new ArrayList<Object>(op.getVariableTypes()), op.propagatesInput());
+    }
+
+    @Override
     public ILogicalOperator visitDataScanOperator(DataSourceScanOperator op, Void arg) throws AlgebricksException {
         ArrayList<LogicalVariable> newInputList = new ArrayList<LogicalVariable>();
         newInputList.addAll(op.getVariables());
@@ -276,7 +286,8 @@
     }
 
     @Override
-    public ILogicalOperator visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, Void arg) throws AlgebricksException {
+    public ILogicalOperator visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, Void arg)
+            throws AlgebricksException {
         List<Mutable<ILogicalExpression>> newKeyExpressions = new ArrayList<Mutable<ILogicalExpression>>();
         deepCopyExpressionRefs(newKeyExpressions, op.getPrimaryKeyExpressions());
         List<Mutable<ILogicalExpression>> newLSMComponentFilterExpressions = new ArrayList<Mutable<ILogicalExpression>>();
@@ -299,9 +310,9 @@
                 ((AbstractLogicalExpression) op.getFilterExpression()).cloneExpression());
         List<Mutable<ILogicalExpression>> newLSMComponentFilterExpressions = new ArrayList<Mutable<ILogicalExpression>>();
         deepCopyExpressionRefs(newLSMComponentFilterExpressions, op.getAdditionalFilteringExpressions());
-        IndexInsertDeleteUpsertOperator indexInsertDeleteOp = new IndexInsertDeleteUpsertOperator(op.getDataSourceIndex(),
-                newPrimaryKeyExpressions, newSecondaryKeyExpressions, newFilterExpression, op.getOperation(),
-                op.isBulkload());
+        IndexInsertDeleteUpsertOperator indexInsertDeleteOp = new IndexInsertDeleteUpsertOperator(
+                op.getDataSourceIndex(), newPrimaryKeyExpressions, newSecondaryKeyExpressions, newFilterExpression,
+                op.getOperation(), op.isBulkload());
         indexInsertDeleteOp.setAdditionalFilteringExpressions(newLSMComponentFilterExpressions);
         return indexInsertDeleteOp;
     }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/ProducedVariableVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/ProducedVariableVisitor.java
index 8a0f156..b05bdf5 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/ProducedVariableVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/ProducedVariableVisitor.java
@@ -47,6 +47,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -223,6 +224,12 @@
     }
 
     @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg) throws AlgebricksException {
+        producedVariables.addAll(op.getVariables());
+        return null;
+    }
+
+    @Override
     public Void visitUnnestOperator(UnnestOperator op, Void arg) throws AlgebricksException {
         return visitUnnestNonMapOperator(op);
     }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SchemaVariableVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SchemaVariableVisitor.java
index 7a2f229..ea6f6aa 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SchemaVariableVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SchemaVariableVisitor.java
@@ -45,6 +45,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -244,6 +245,12 @@
     }
 
     @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg) throws AlgebricksException {
+        standardLayout(op);
+        return null;
+    }
+
+    @Override
     public Void visitUnnestOperator(UnnestOperator op, Void arg) throws AlgebricksException {
         standardLayout(op);
         return null;
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
index 55dc11a..4061497 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/SubstituteVariableVisitor.java
@@ -29,6 +29,7 @@
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.expressions.IVariableTypeEnvironment;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestNonMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
@@ -41,9 +42,10 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.GroupByOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IndexInsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
-import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -306,13 +308,13 @@
     public Void visitIntersectOperator(IntersectOperator op, Pair<LogicalVariable, LogicalVariable> pair)
             throws AlgebricksException {
         for (int i = 0; i < op.getOutputVars().size(); i++) {
-            if (op.getOutputVars().get(i).equals(pair.first)){
+            if (op.getOutputVars().get(i).equals(pair.first)) {
                 op.getOutputVars().set(i, pair.second);
             }
         }
-        for(int i = 0; i < op.getNumInput(); i++){
-            for (int j = 0; j < op.getInputVariables(i).size(); j++){
-                if (op.getInputVariables(i).get(j).equals(pair.first)){
+        for (int i = 0; i < op.getNumInput(); i++) {
+            for (int j = 0; j < op.getInputVariables(i).size(); j++) {
+                if (op.getInputVariables(i).get(j).equals(pair.first)) {
                     op.getInputVariables(i).set(j, pair.second);
                 }
             }
@@ -323,16 +325,28 @@
     @Override
     public Void visitUnnestMapOperator(UnnestMapOperator op, Pair<LogicalVariable, LogicalVariable> pair)
             throws AlgebricksException {
+        substituteVarsForAbstractUnnestMapOp(op, pair);
+        return null;
+    }
+
+    @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op,
+            Pair<LogicalVariable, LogicalVariable> pair) throws AlgebricksException {
+        substituteVarsForAbstractUnnestMapOp(op, pair);
+        return null;
+    }
+
+    private void substituteVarsForAbstractUnnestMapOp(AbstractUnnestMapOperator op,
+            Pair<LogicalVariable, LogicalVariable> pair) throws AlgebricksException {
         List<LogicalVariable> variables = op.getVariables();
         for (int i = 0; i < variables.size(); i++) {
             if (variables.get(i) == pair.first) {
                 variables.set(i, pair.second);
-                return null;
+                return;
             }
         }
         op.getExpressionRef().getValue().substituteVar(pair.first, pair.second);
         substVarTypes(op, pair);
-        return null;
     }
 
     @Override
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/UsedVariableVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/UsedVariableVisitor.java
index 5cfd619..6e895bd 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/UsedVariableVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/operators/logical/visitors/UsedVariableVisitor.java
@@ -30,6 +30,7 @@
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import org.apache.hyracks.algebricks.core.algebra.base.IPhysicalOperator;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
@@ -44,6 +45,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -310,13 +312,23 @@
 
     @Override
     public Void visitUnnestMapOperator(UnnestMapOperator op, Void arg) {
+        getUsedVarsFromExprAndFilterExpr(op);
+        return null;
+    }
+
+    @Override
+    public Void visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg) throws AlgebricksException {
+        getUsedVarsFromExprAndFilterExpr(op);
+        return null;
+    }
+
+    private void getUsedVarsFromExprAndFilterExpr(AbstractUnnestMapOperator op) {
         op.getExpressionRef().getValue().getUsedVariables(usedVariables);
         if (op.getAdditionalFilteringExpressions() != null) {
             for (Mutable<ILogicalExpression> e : op.getAdditionalFilteringExpressions()) {
                 e.getValue().getUsedVariables(usedVariables);
             }
         }
-        return null;
     }
 
     @Override
@@ -357,14 +369,14 @@
 
     @Override
     public Void visitInsertDeleteUpsertOperator(InsertDeleteUpsertOperator op, Void arg) {
-        //1. The record variable
+        // 1. The record variable
         op.getPayloadExpression().getValue().getUsedVariables(usedVariables);
 
-        //2. The primary key variables
+        // 2. The primary key variables
         for (Mutable<ILogicalExpression> e : op.getPrimaryKeyExpressions()) {
             e.getValue().getUsedVariables(usedVariables);
         }
-        //3. The filters variables
+        // 3. The filters variables
         if (op.getAdditionalFilteringExpressions() != null) {
             for (Mutable<ILogicalExpression> e : op.getAdditionalFilteringExpressions()) {
                 e.getValue().getUsedVariables(usedVariables);
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
index ca35284..b85202d 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/prettyprint/LogicalOperatorPrettyPrintVisitor.java
@@ -28,6 +28,7 @@
 import org.apache.hyracks.algebricks.core.algebra.base.ILogicalPlan;
 import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractOperatorWithNestedPlans;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AggregateOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.AssignOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.DataSourceScanOperator;
@@ -43,6 +44,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator.Kind;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -306,8 +308,19 @@
 
     @Override
     public String visitUnnestMapOperator(UnnestMapOperator op, Integer indent) throws AlgebricksException {
+        return printAbstractUnnestMapOperator(op, indent, "unnest-map");
+    }
+
+    @Override
+    public String visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Integer indent)
+            throws AlgebricksException {
+        return printAbstractUnnestMapOperator(op, indent, "left-outer-unnest-map");
+    }
+
+    private String printAbstractUnnestMapOperator(AbstractUnnestMapOperator op, Integer indent, String opSignature)
+            throws AlgebricksException {
         StringBuilder buffer = new StringBuilder();
-        addIndent(buffer, indent).append("unnest-map " + op.getVariables() + " <- "
+        addIndent(buffer, indent).append(opSignature + " " + op.getVariables() + " <- "
                 + op.getExpressionRef().getValue().accept(exprVisitor, indent));
         return buffer.toString();
     }
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/properties/TypePropagationPolicy.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/properties/TypePropagationPolicy.java
index fa9ad05..e011a7f 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/properties/TypePropagationPolicy.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/properties/TypePropagationPolicy.java
@@ -70,9 +70,9 @@
                 Object t = typeEnvs[i].getTypeEnv().getVarType(var, nonNullVariableList,
                         correlatedNullableVariableLists);
                 if (t != null) {
-                    if (i == 0) { // inner branch
+                    if (i == 0) { // outer branch
                         return t;
-                    } else { // outer branch
+                    } else { // inner branch
                         boolean nonNullVarIsProduced = false;
                         for (LogicalVariable v : nonNullVariableList) {
                             if (v == var) {
diff --git a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
index ebb0198..298daf9 100644
--- a/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
+++ b/algebricks/algebricks-core/src/main/java/org/apache/hyracks/algebricks/core/algebra/visitors/ILogicalOperatorVisitor.java
@@ -33,6 +33,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InsertDeleteUpsertOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -103,6 +104,8 @@
 
     public R visitUnnestMapOperator(UnnestMapOperator op, T arg) throws AlgebricksException;
 
+    public R visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, T arg) throws AlgebricksException;
+
     public R visitDataScanOperator(DataSourceScanOperator op, T arg) throws AlgebricksException;
 
     public R visitDistinctOperator(DistinctOperator op, T arg) throws AlgebricksException;
diff --git a/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/subplan/ReplaceNtsWithSubplanInputOperatorVisitor.java b/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/subplan/ReplaceNtsWithSubplanInputOperatorVisitor.java
index 551b5d0..d36bff9 100644
--- a/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/subplan/ReplaceNtsWithSubplanInputOperatorVisitor.java
+++ b/algebricks/algebricks-rewriter/src/main/java/org/apache/hyracks/algebricks/rewriter/rules/subplan/ReplaceNtsWithSubplanInputOperatorVisitor.java
@@ -41,6 +41,7 @@
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.InnerJoinOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.IntersectOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterJoinOperator;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.LeftOuterUnnestMapOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.LimitOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.MaterializeOperator;
 import org.apache.hyracks.algebricks.core.algebra.operators.logical.NestedTupleSourceOperator;
@@ -62,9 +63,10 @@
 import org.apache.hyracks.algebricks.core.algebra.visitors.IQueryOperatorVisitor;
 
 /**
- * This visitor replaces NTS' in a subplan with its input operator or its deep copies.
- * Note that this visitor can only be used in the rule EliminateSubplanWithInputCardinalityOneRule,
- * for cases where the Subplan input operator is of cardinality one and its variables are not needed after
+ * This visitor replaces NTS' in a subplan with its input operator or its deep
+ * copies. Note that this visitor can only be used in the rule
+ * EliminateSubplanWithInputCardinalityOneRule, for cases where the Subplan
+ * input operator is of cardinality one and its variables are not needed after
  * the Subplan.
  */
 class ReplaceNtsWithSubplanInputOperatorVisitor implements IQueryOperatorVisitor<ILogicalOperator, Void> {
@@ -74,7 +76,8 @@
     // The input operator to the subplan.
     private final ILogicalOperator subplanInputOperator;
 
-    // The map that maps the input variables to the subplan to their deep-copied variables.
+    // The map that maps the input variables to the subplan to their deep-copied
+    // variables.
     private final Map<LogicalVariable, LogicalVariable> varMap = new HashMap<>();
 
     // Whether the original copy has been used.
@@ -216,6 +219,12 @@
     }
 
     @Override
+    public ILogicalOperator visitLeftOuterUnnestMapOperator(LeftOuterUnnestMapOperator op, Void arg)
+            throws AlgebricksException {
+        return visit(op);
+    }
+
+    @Override
     public ILogicalOperator visitDataScanOperator(DataSourceScanOperator op, Void arg) throws AlgebricksException {
         return visit(op);
     }
@@ -245,7 +254,8 @@
         for (Mutable<ILogicalOperator> childRef : op.getInputs()) {
             ILogicalOperator newChild = childRef.getValue().accept(this, null);
             childRef.setValue(newChild);
-            // Replaces variables in op with the mapping obtained from one child.
+            // Replaces variables in op with the mapping obtained from one
+            // child.
             VariableUtilities.substituteVariables(op, varMap, ctx);
             // Keep the map from current child and move to the next child.
             varMapSnapshots.add(new HashMap<LogicalVariable, LogicalVariable>(varMap));