[NO-ISSUE][GRAPHIX] Large Graphix update.

Large commit for the following:
- Using AbstractClauseExtension.
- LEFT-MATCH now defaults to a non-foldable-action.
- Refactor of some docstrings to use HTML lists.
- Starting work towards adding using SWITCH and CYCLE at Graphix.
- Adding support for implicit correlated vertex JOINs.
- Adding support for graphs with duplicate schema edge labels.
- Adding support for unconditional schema decoration.
- Adding support for negated edge labels.
- Total revamp for schema resolution: we now take an exhaustive approach.
- Adding support for specifying Graphix compiler options in the config file.

Change-Id: I120362128a5557f7de8904b86bacde3b606760db
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb-graph/+/17235
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Glenn Galvizo <ggalvizo@uci.edu>
diff --git a/asterix-graphix/pom.xml b/asterix-graphix/pom.xml
index e1d517c..d20bcfe 100644
--- a/asterix-graphix/pom.xml
+++ b/asterix-graphix/pom.xml
@@ -190,6 +190,11 @@
 
   <dependencies>
     <dependency>
+      <groupId>com.github.dpaukov</groupId>
+      <artifactId>combinatoricslib3</artifactId>
+      <version>3.3.0</version>
+    </dependency>
+    <dependency>
       <groupId>org.apache.asterix</groupId>
       <artifactId>asterix-om</artifactId>
       <version>${asterix.version}</version>
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/ElementEvaluationOption.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/ElementEvaluationOption.java
new file mode 100644
index 0000000..a44fc74
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/ElementEvaluationOption.java
@@ -0,0 +1,39 @@
+/*
+ * 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.asterix.graphix.algebra.compiler.option;
+
+import java.util.Locale;
+
+public enum ElementEvaluationOption implements IGraphixCompilerOption {
+    EXPAND_AND_UNION,
+    SWITCH_AND_CYCLE;
+
+    public static final String OPTION_KEY_NAME = "graphix.evaluation.default";
+    public static final ElementEvaluationOption OPTION_DEFAULT = EXPAND_AND_UNION;
+
+    @Override
+    public String getOptionValue() {
+        return name().toLowerCase(Locale.ROOT).replace("_", "-");
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
similarity index 73%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
index 4509792..5cb0027 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
@@ -16,11 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.algebra.compiler.option;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+public interface IGraphixCompilerOption {
+    String getOptionValue();
 
-@FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+    String getDisplayName();
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateEdgeOption.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateEdgeOption.java
new file mode 100644
index 0000000..e67c6cb
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateEdgeOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.asterix.graphix.algebra.compiler.option;
+
+import java.util.Locale;
+
+public enum SchemaDecorateEdgeOption implements IGraphixCompilerOption {
+    NEVER,
+    AS_NEEDED,
+    ALWAYS;
+
+    public static final String OPTION_KEY_NAME = "graphix.schema-decorate.edge";
+    public static final SchemaDecorateEdgeOption OPTION_DEFAULT = AS_NEEDED;
+
+    @Override
+    public String getOptionValue() {
+        return name().toLowerCase(Locale.ROOT).replace("_", "-");
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateVertexOption.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateVertexOption.java
new file mode 100644
index 0000000..7a0cb73
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateVertexOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.asterix.graphix.algebra.compiler.option;
+
+import java.util.Locale;
+
+public enum SchemaDecorateVertexOption implements IGraphixCompilerOption {
+    NEVER,
+    AS_NEEDED,
+    ALWAYS;
+
+    public static final String OPTION_KEY_NAME = "graphix.schema-decorate.vertex";
+    public static final SchemaDecorateVertexOption OPTION_DEFAULT = AS_NEEDED;
+
+    @Override
+    public String getOptionValue() {
+        return name().toLowerCase(Locale.ROOT).replace("_", "-");
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsNavigationOption.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsNavigationOption.java
new file mode 100644
index 0000000..7715387
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsNavigationOption.java
@@ -0,0 +1,40 @@
+/*
+ * 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.asterix.graphix.algebra.compiler.option;
+
+import java.util.Locale;
+
+public enum SemanticsNavigationOption implements IGraphixCompilerOption {
+    NO_REPEAT_VERTICES,
+    NO_REPEAT_EDGES,
+    NO_REPEAT_ANYTHING;
+
+    public static final String OPTION_KEY_NAME = "graphix.semantics.navigation";
+    public static final SemanticsNavigationOption OPTION_DEFAULT = NO_REPEAT_ANYTHING;
+
+    @Override
+    public String getOptionValue() {
+        return name().toLowerCase(Locale.ROOT).replace("_", "-");
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsPatternOption.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsPatternOption.java
new file mode 100644
index 0000000..559686f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsPatternOption.java
@@ -0,0 +1,41 @@
+/*
+ * 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.asterix.graphix.algebra.compiler.option;
+
+import java.util.Locale;
+
+public enum SemanticsPatternOption implements IGraphixCompilerOption {
+    ISOMORPHISM,
+    VERTEX_ISOMORPHISM,
+    EDGE_ISOMORPHISM,
+    HOMOMORPHISM;
+
+    public static final String OPTION_KEY_NAME = "graphix.semantics.pattern";
+    public static final SemanticsPatternOption OPTION_DEFAULT = ISOMORPHISM;
+
+    @Override
+    public String getOptionValue() {
+        return name().toLowerCase(Locale.ROOT).replace("_", "-");
+    }
+
+    @Override
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
index 79a2b68..06f28c5 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
@@ -23,21 +23,20 @@
 import org.apache.asterix.algebra.base.ILangExpressionToPlanTranslatorFactory;
 import org.apache.asterix.compiler.provider.IRuleSetFactory;
 import org.apache.asterix.compiler.provider.SqlppCompilationProvider;
+import org.apache.asterix.graphix.algebra.compiler.option.ElementEvaluationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateEdgeOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateVertexOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsNavigationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsPatternOption;
 import org.apache.asterix.graphix.algebra.translator.GraphixExpressionToPlanTranslatorFactory;
 import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewriterFactory;
-import org.apache.asterix.graphix.lang.rewrites.print.GraphixASTPrintVisitorFactory;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewriterFactory;
+import org.apache.asterix.graphix.lang.rewrite.print.GraphixASTPrintVisitorFactory;
 import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IRewriterFactory;
 
 public class GraphixCompilationProvider extends SqlppCompilationProvider {
-    public static final String PRINT_REWRITE_METADATA_CONFIG = "graphix.print-rewrite";
-    public static final String RESOLVER_METADATA_CONFIG = "graphix.resolver";
-    public static final String RESOLVER_ITERATION_MAX_METADATA_CONFIG = "graphix.max-resolution-iterations";
-    public static final String MATCH_EVALUATION_METADATA_CONFIG = "graphix.match-evaluation";
-    public static final String EDGE_STRATEGY_METADATA_CONFIG = "graphix.edge-strategy";
-
     @Override
     public IParserFactory getParserFactory() {
         return new GraphixParserFactory();
@@ -66,8 +65,11 @@
     @Override
     public Set<String> getCompilerOptions() {
         Set<String> parentConfigurableParameters = super.getCompilerOptions();
-        parentConfigurableParameters.addAll(Set.of(PRINT_REWRITE_METADATA_CONFIG, MATCH_EVALUATION_METADATA_CONFIG,
-                RESOLVER_METADATA_CONFIG, RESOLVER_ITERATION_MAX_METADATA_CONFIG, EDGE_STRATEGY_METADATA_CONFIG));
+        parentConfigurableParameters.add(ElementEvaluationOption.OPTION_KEY_NAME);
+        parentConfigurableParameters.add(SchemaDecorateEdgeOption.OPTION_KEY_NAME);
+        parentConfigurableParameters.add(SchemaDecorateVertexOption.OPTION_KEY_NAME);
+        parentConfigurableParameters.add(SemanticsNavigationOption.OPTION_KEY_NAME);
+        parentConfigurableParameters.add(SemanticsPatternOption.OPTION_KEY_NAME);
         return parentConfigurableParameters;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/translator/GraphixExpressionToPlanTranslator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/translator/GraphixExpressionToPlanTranslator.java
index 12a7f39..c720a17 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/translator/GraphixExpressionToPlanTranslator.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/translator/GraphixExpressionToPlanTranslator.java
@@ -16,17 +16,96 @@
  */
 package org.apache.asterix.graphix.algebra.translator;
 
+import static org.apache.asterix.graphix.runtime.function.GraphixFunctionInfoCollection.*;
+
+import java.util.Iterator;
 import java.util.Map;
 
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.extension.IGraphixVisitorExtension;
+import org.apache.asterix.graphix.lang.clause.extension.LowerListClauseExtension;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.translator.SqlppExpressionToPlanTranslator;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalVariable;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.UnnestOperator;
 
 public class GraphixExpressionToPlanTranslator extends SqlppExpressionToPlanTranslator {
     public GraphixExpressionToPlanTranslator(MetadataProvider metadataProvider, int currentVarCounter,
             Map<VarIdentifier, IAObject> externalVars) throws AlgebricksException {
         super(metadataProvider, currentVarCounter, externalVars);
     }
+
+    @Override
+    public Pair<ILogicalOperator, LogicalVariable> visit(IVisitorExtension ve, Mutable<ILogicalOperator> tupSource)
+            throws CompilationException {
+        if (ve instanceof IGraphixVisitorExtension) {
+            IGraphixVisitorExtension gve = (IGraphixVisitorExtension) ve;
+            if (gve.getKind() != IGraphixVisitorExtension.Kind.LOWER_LIST) {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                        "Encountered illegal type of Graphix visitor extension!");
+            }
+            return translateLowerListClause((LowerListClauseExtension) gve, tupSource);
+
+        } else {
+            return super.visit(ve, tupSource);
+        }
+    }
+
+    public Pair<ILogicalOperator, LogicalVariable> translateLowerListClause(LowerListClauseExtension llce,
+            Mutable<ILogicalOperator> tupSource) throws CompilationException {
+        ClauseCollection clauseCollection = llce.getLowerListClause().getClauseCollection();
+        Iterator<AbstractClause> clauseIterator = clauseCollection.iterator();
+        if (!clauseIterator.hasNext()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Encountered empty lower-clause collection!");
+        }
+
+        // Translate our leading clause in our collection.
+        AbstractBinaryCorrelateClause leadingLowerClause = (AbstractBinaryCorrelateClause) clauseIterator.next();
+        LogicalVariable leftVar = context.newVarFromExpression(leadingLowerClause.getRightVariable());
+        Mutable<ILogicalOperator> topOpRef = new MutableObject<>();
+        //        if (tupSource.getValue() instanceof GraphTupleSourceOperator) {
+        //            // Do not ignore our condition.
+        //            topOpRef = new MutableObject<>(leadingLowerClause.accept(this, tupSource).first);
+        //
+        //        } else {
+        // Our first clause is functionally equivalent to the left expression of a FROM-TERM. Ignore our condition.
+        Expression leftLangExpr = leadingLowerClause.getRightExpression();
+        Pair<ILogicalExpression, Mutable<ILogicalOperator>> eo = langExprToAlgExpression(leftLangExpr, tupSource);
+        Pair<ILogicalExpression, Mutable<ILogicalOperator>> unnestExpr = makeUnnestExpression(eo.first, eo.second);
+        UnnestOperator unnestOp = new UnnestOperator(leftVar, new MutableObject<>(unnestExpr.first));
+        unnestOp.getInputs().add(unnestExpr.second);
+        unnestOp.setSourceLocation(clauseCollection.getSourceLocation());
+        topOpRef.setValue(unnestOp);
+        //        }
+
+        // The remainder of our clauses are either JOINs, UNNESTs, LETs, WHEREs, or GRAPH-CLAUSEs.
+        while (clauseIterator.hasNext()) {
+            AbstractClause workingLowerClause = clauseIterator.next();
+            //            if (workingLowerClause instanceof LowerSwitchClause) {
+            //                LowerSwitchClause lowerBFSClause = (LowerSwitchClause) workingLowerClause;
+            //                LowerSwitchClauseExtension visitorExtension =
+            //                        (LowerSwitchClauseExtension) lowerBFSClause.getVisitorExtension();
+            //                topOpRef = new MutableObject<>(translateLowerGraphClause(visitorExtension, topOpRef).first);
+            //
+            //            } else {
+            topOpRef = new MutableObject<>(workingLowerClause.accept(this, topOpRef).first);
+            //            }
+        }
+        return new Pair<>(topOpRef.getValue(), leftVar);
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslator.java
index c342b10..e24d930 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslator.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslator.java
@@ -20,9 +20,11 @@
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
 
 import org.apache.asterix.app.translator.QueryTranslator;
 import org.apache.asterix.common.api.IResponsePrinter;
@@ -33,8 +35,8 @@
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.compiler.provider.ILangCompilationProvider;
 import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
-import org.apache.asterix.graphix.lang.rewrites.GraphixQueryRewriter;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixQueryRewriter;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
@@ -61,16 +63,21 @@
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.translator.IRequestParameters;
 import org.apache.asterix.translator.SessionOutput;
+import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public class GraphixQueryTranslator extends QueryTranslator {
-    Set<DeclareGraphStatement> declareGraphStatements = new HashSet<>();
+    private final Set<DeclareGraphStatement> declareGraphStatements = new HashSet<>();
+    private final Map<String, String> configFileOptions;
 
     public GraphixQueryTranslator(ICcApplicationContext appCtx, List<Statement> statements, SessionOutput output,
             ILangCompilationProvider compilationProvider, ExecutorService executorService,
-            IResponsePrinter responsePrinter) {
+            IResponsePrinter responsePrinter, List<Pair<String, String>> configFileOptions) {
         super(appCtx, statements, output, compilationProvider, executorService, responsePrinter);
+
+        // We are given the following information from our cluster-controller config file.
+        this.configFileOptions = configFileOptions.stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
     }
 
     public GraphixQueryRewriter getQueryRewriter() {
@@ -93,15 +100,17 @@
             List<FunctionDecl> declaredFunctions, List<ViewDecl> declaredViews, IWarningCollector warningCollector,
             int varCounter) {
         return new GraphixRewritingContext(metadataProvider, declaredFunctions, declaredViews, declareGraphStatements,
-                warningCollector, varCounter);
+                warningCollector, varCounter, configFileOptions);
     }
 
     /**
      * To create a view, we must perform the following:
-     * a) Check the view body for any named graphs.
-     * b) Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
-     * expressions will be recorded in the "Dataset" dataset.
-     * c) Record any graph-related dependencies for the view in our metadata.
+     * <ol>
+     *  <li>Check the view body for any named graphs.</li>
+     *  <li>Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
+     *  expressions will be recorded in the "Dataset" dataset.</li>
+     *  <li>Record any graph-related dependencies for the view in our metadata.</li>
+     * </ol>
      */
     @Override
     protected CreateResult doCreateView(MetadataProvider metadataProvider, CreateViewStatement cvs,
@@ -153,10 +162,12 @@
 
     /**
      * To create a function, we must perform the following:
-     * a) Check the function body for any named graphs.
-     * b) Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
-     * expressions will be recorded in the "Function" dataset.
-     * c) Record any graph-related dependencies for the function in our metadata.
+     * <ol>
+     *  <li>Check the function body for any named graphs.</li>
+     *  <li>Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
+     *  expressions will be recorded in the "Function" dataset.</li>
+     *  <li>Record any graph-related dependencies for the function in our metadata.</li>
+     * </ol>
      */
     @Override
     protected CreateResult doCreateFunction(MetadataProvider metadataProvider, CreateFunctionStatement cfs,
@@ -229,8 +240,10 @@
 
     /**
      * Before dropping a function, we perform the following:
-     * 1. Check if any of our existing graphs depend on the function to-be-dropped.
-     * 2. Remove the GraphDependency record for the function to-be-dropped if it exists.
+     * <ol>
+     *  <li>Check if any of our existing graphs depend on the function to-be-dropped.</li>
+     *  <li>Remove the GraphDependency record for the function to-be-dropped if it exists.</li>
+     * </ol>
      */
     @Override
     protected void handleFunctionDropStatement(MetadataProvider metadataProvider, Statement stmt,
@@ -264,8 +277,10 @@
 
     /**
      * Before dropping a view, we perform the following:
-     * 1. Check if any of our existing graphs depend on the view to-be-dropped.
-     * 2. Remove the GraphDependency record for the view to-be-dropped if it exists.
+     * <ol>
+     *  <li>Check if any of our existing graphs depend on the view to-be-dropped.</li>
+     *  <li>Remove the GraphDependency record for the view to-be-dropped if it exists.</li>
+     * </ol>
      */
     @Override
     public void handleViewDropStatement(MetadataProvider metadataProvider, Statement stmt) throws Exception {
@@ -313,9 +328,12 @@
 
     /**
      * Before dropping a dataverse, we perform the following:
-     * 1. Check if any other entities outside the dataverse to-be-dropped depend on any entities inside the dataverse.
-     * 2. Remove all GraphDependency records associated with the dataverse to-be-dropped.
-     * 3. Remove all Graph records associated with the dataverse to-be-dropped.
+     * <ol>
+     *  <li>Check if any other entities outside the dataverse to-be-dropped depend on any entities inside the
+     *  dataverse.</li>
+     *  <li>Remove all GraphDependency records associated with the dataverse to-be-dropped.</li>
+     *  <li>Remove all Graph records associated with the dataverse to-be-dropped.</li>
+     * </ol>
      */
     @Override
     protected void handleDataverseDropStatement(MetadataProvider metadataProvider, Statement stmt,
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslatorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslatorFactory.java
index a0c4d2f..7fad326 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslatorFactory.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/app/translator/GraphixQueryTranslatorFactory.java
@@ -28,12 +28,20 @@
 import org.apache.asterix.compiler.provider.ILangCompilationProvider;
 import org.apache.asterix.lang.common.base.Statement;
 import org.apache.asterix.translator.SessionOutput;
+import org.apache.hyracks.algebricks.common.utils.Pair;
 
 public class GraphixQueryTranslatorFactory extends DefaultStatementExecutorFactory {
+    private List<Pair<String, String>> configFileProvidedOptions;
+
     @Override
     public QueryTranslator create(ICcApplicationContext appCtx, List<Statement> statements, SessionOutput output,
             ILangCompilationProvider compilationProvider, IStorageComponentProvider storageComponentProvider,
             IResponsePrinter printer) {
-        return new GraphixQueryTranslator(appCtx, statements, output, compilationProvider, executorService, printer);
+        return new GraphixQueryTranslator(appCtx, statements, output, compilationProvider, executorService, printer,
+                configFileProvidedOptions);
+    }
+
+    public void setConfigFileProvidedOptions(List<Pair<String, String>> configFileProvidedOptions) {
+        this.configFileProvidedOptions = configFileProvidedOptions;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/EdgeIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/EdgeIdentifier.java
new file mode 100644
index 0000000..925515a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/EdgeIdentifier.java
@@ -0,0 +1,90 @@
+/*
+ * 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.asterix.graphix.common.metadata;
+
+import java.util.Objects;
+
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * A unique identifier for an edge. An edge is uniquely identified by:
+ * <ul>
+ *  <li>The graph identifier associated with the edge itself.</li>
+ *  <li>The label associated with the source vertex.</li>
+ *  <li>The label associated with the destination vertex.</li>
+ *  <li>The label associated with the edge itself.</li>
+ * </ul>
+ */
+public class EdgeIdentifier implements IElementIdentifier {
+    private static final long serialVersionUID = 1L;
+    private final GraphIdentifier graphIdentifier;
+    private final ElementLabel sourceLabel;
+    private final ElementLabel edgeLabel;
+    private final ElementLabel destinationLabel;
+
+    public EdgeIdentifier(GraphIdentifier graphIdentifier, ElementLabel sourceLabel, ElementLabel edgeLabel,
+            ElementLabel destinationLabel) {
+        this.graphIdentifier = Objects.requireNonNull(graphIdentifier);
+        this.sourceLabel = Objects.requireNonNull(sourceLabel);
+        this.edgeLabel = Objects.requireNonNull(edgeLabel);
+        this.destinationLabel = Objects.requireNonNull(destinationLabel);
+    }
+
+    @Override
+    public GraphIdentifier getGraphIdentifier() {
+        return graphIdentifier;
+    }
+
+    public ElementLabel getSourceLabel() {
+        return sourceLabel;
+    }
+
+    public ElementLabel getEdgeLabel() {
+        return edgeLabel;
+    }
+
+    public ElementLabel getDestinationLabel() {
+        return destinationLabel;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s (:%s)-[:%s]->(:%s)", graphIdentifier, sourceLabel, edgeLabel, destinationLabel);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof EdgeIdentifier) {
+            EdgeIdentifier that = (EdgeIdentifier) o;
+            return Objects.equals(this.graphIdentifier, that.graphIdentifier)
+                    && Objects.equals(this.sourceLabel, that.sourceLabel)
+                    && Objects.equals(this.edgeLabel, that.edgeLabel)
+                    && Objects.equals(this.destinationLabel, that.destinationLabel);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(graphIdentifier, sourceLabel, edgeLabel, destinationLabel);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
deleted file mode 100644
index f440c2f..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.asterix.graphix.common.metadata;
-
-import java.io.Serializable;
-import java.util.Objects;
-
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-
-/**
- * A unique identifier for a graph element (vertex or edge). A graph element is uniquely identified by:
- * 1. The graph identifier associated with the graph element itself.
- * 2. The kind of the element (vertex or edge).
- * 3. The label associated with the element itself- a graph element has only one label in our user model.
- */
-public class GraphElementIdentifier implements Serializable {
-    private static final long serialVersionUID = 1L;
-    private final GraphIdentifier graphIdentifier;
-    private final Kind elementKind;
-    private final ElementLabel elementLabel;
-
-    public GraphElementIdentifier(GraphIdentifier graphIdentifier, Kind elementKind, ElementLabel elementLabel) {
-        this.graphIdentifier = graphIdentifier;
-        this.elementKind = elementKind;
-        this.elementLabel = elementLabel;
-    }
-
-    public GraphIdentifier getGraphIdentifier() {
-        return graphIdentifier;
-    }
-
-    public Kind getElementKind() {
-        return elementKind;
-    }
-
-    public ElementLabel getElementLabel() {
-        return elementLabel;
-    }
-
-    @Override
-    public String toString() {
-        return graphIdentifier + "#" + elementLabel + " ( " + elementKind + " )";
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o instanceof GraphElementIdentifier) {
-            GraphElementIdentifier that = (GraphElementIdentifier) o;
-            return graphIdentifier.equals(that.graphIdentifier) && elementKind.equals(that.elementKind)
-                    && elementLabel.equals(that.elementLabel);
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(graphIdentifier, elementKind, elementLabel);
-    }
-
-    public enum Kind {
-        VERTEX,
-        EDGE
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
index c3cc5bd..2172425 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
@@ -25,8 +25,10 @@
 
 /**
  * A unique identifier for a graph. A graph is uniquely identified by:
- * 1. The dataverse associated with the graph. A graph identifier must always belong to some dataverse.
- * 2. The name of the graph. Anonymous graphs should have a name generated from its respective GRAPH-CONSTRUCTOR.
+ * <ul>
+ *  <li>The dataverse associated with the graph. A graph identifier must always belong to some dataverse.</li>
+ *  <li>The name of the graph. Anonymous graphs should have a name generated from its respective GRAPH-CONSTRUCTOR.</li>
+ * </ul>
  */
 public class GraphIdentifier implements Serializable {
     private static final long serialVersionUID = 1L;
@@ -58,7 +60,7 @@
         }
         if (o instanceof GraphIdentifier) {
             GraphIdentifier that = (GraphIdentifier) o;
-            return dataverseName.equals(that.dataverseName) && Objects.equals(graphName, that.graphName);
+            return Objects.equals(this.dataverseName, that.dataverseName) && Objects.equals(graphName, that.graphName);
         }
         return false;
     }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/IElementIdentifier.java
similarity index 75%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/IElementIdentifier.java
index 4509792..2daff9f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/IElementIdentifier.java
@@ -16,11 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.common.metadata;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+import java.io.Serializable;
 
 @FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+public interface IElementIdentifier extends Serializable {
+    GraphIdentifier getGraphIdentifier();
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/VertexIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/VertexIdentifier.java
new file mode 100644
index 0000000..12fdb9d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/VertexIdentifier.java
@@ -0,0 +1,73 @@
+/*
+ * 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.asterix.graphix.common.metadata;
+
+import java.util.Objects;
+
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * A unique identifier for a vertex. A vertex is uniquely identified by:
+ * <ul>
+ *  <li>The graph identifier associated with the vertex itself.</li>
+ *  <li>The label associated with the vertex itself.</li>
+ * </ul>
+ */
+public class VertexIdentifier implements IElementIdentifier {
+    private static final long serialVersionUID = 1L;
+    private final GraphIdentifier graphIdentifier;
+    private final ElementLabel vertexLabel;
+
+    public VertexIdentifier(GraphIdentifier graphIdentifier, ElementLabel vertexLabel) {
+        this.graphIdentifier = graphIdentifier;
+        this.vertexLabel = vertexLabel;
+    }
+
+    @Override
+    public GraphIdentifier getGraphIdentifier() {
+        return graphIdentifier;
+    }
+
+    public ElementLabel getVertexLabel() {
+        return vertexLabel;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s (:%s)", graphIdentifier, vertexLabel);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof VertexIdentifier) {
+            VertexIdentifier that = (VertexIdentifier) o;
+            return Objects.equals(this.graphIdentifier, that.graphIdentifier)
+                    && Objects.equals(this.vertexLabel, that.vertexLabel);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(graphIdentifier, vertexLabel);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixQueryTranslatorExtension.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixQueryTranslatorExtension.java
index b6f7753..139c8a8 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixQueryTranslatorExtension.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixQueryTranslatorExtension.java
@@ -30,7 +30,7 @@
     public static final ExtensionId GRAPHIX_QUERY_TRANSLATOR_EXTENSION_ID =
             new ExtensionId(GraphixQueryTranslatorExtension.class.getSimpleName(), 0);
 
-    private static final IStatementExecutorFactory INSTANCE = new GraphixQueryTranslatorFactory();
+    private static final GraphixQueryTranslatorFactory INSTANCE = new GraphixQueryTranslatorFactory();
 
     @Override
     public ExtensionId getId() {
@@ -39,6 +39,7 @@
 
     @Override
     public void configure(List<Pair<String, String>> args) {
+        INSTANCE.setConfigFileProvidedOptions(args);
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionIdentifiers.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionIdentifiers.java
index 50e58b9..a5dc34b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionIdentifiers.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionIdentifiers.java
@@ -69,8 +69,24 @@
     public static final FunctionIdentifier PATH_EDGES =
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "path-edges", 1);
 
+    // Private functions used internally to enforce navigation semantics.
+    public static final FunctionIdentifier IS_DISTINCT_EDGE =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "is-distinct-edge", 2);
+    public static final FunctionIdentifier IS_DISTINCT_VERTEX =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "is-distinct-vertex", 2);
+    public static final FunctionIdentifier IS_DISTINCT_EVERYTHING =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "is-distinct-everything", 3);
+
+    // Private functions used internally to manage a path during navigation.
+    public static final FunctionIdentifier CREATE_INTERNAL_PATH =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "create-internal-path", 1);
+    public static final FunctionIdentifier APPEND_INTERNAL_PATH =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "append-internal-path", 3);
+    public static final FunctionIdentifier MATERIALIZE_PATH =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "materialize-path", 1);
+
     static {
-        // Register all the functions above.
+        // Register the non-internal functions above.
         functionIdentifierMap = new HashMap<>();
         Consumer<FunctionIdentifier> functionRegister = f -> functionIdentifierMap.put(f.getName(), f);
         functionRegister.accept(ELEMENT_LABEL);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionMap.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionMap.java
index d1f3c89..a17ebac 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionMap.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionMap.java
@@ -34,12 +34,14 @@
 import org.apache.asterix.graphix.function.rewrite.PathHopCountRewrite;
 import org.apache.asterix.graphix.function.rewrite.PathVerticesRewrite;
 import org.apache.asterix.graphix.function.rewrite.SchemaAccessRewrite;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixFunctionCallVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.SchemaEnrichmentVisitor;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.SchemaEnrichmentVisitor
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.GraphixFunctionCallVisitor
+ * @see SchemaEnrichmentVisitor
+ * @see GraphixFunctionCallVisitor
  */
 public class GraphixFunctionMap {
     private final static Map<FunctionIdentifier, IFunctionPrepare> graphixFunctionPrepareMap;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/AbstractElementPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/AbstractElementPrepare.java
index d93f670..82ad445 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/AbstractElementPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/AbstractElementPrepare.java
@@ -26,7 +26,7 @@
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
 import org.apache.asterix.graphix.lang.annotation.GraphixSchemaAnnotation;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldBinding;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDestVertexPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDestVertexPrepare.java
index 7f95f28..affe5dd 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDestVertexPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDestVertexPrepare.java
@@ -25,7 +25,6 @@
 import org.apache.asterix.lang.common.expression.FieldBinding;
 import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.RecordConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.literal.StringLiteral;
 import org.apache.asterix.lang.common.struct.Identifier;
 
@@ -42,9 +41,8 @@
         EdgeDescriptor.EdgeDirection edgeDirection = edgeDescriptor.getEdgeDirection();
         VertexPatternExpr destVertexExpr = (edgeDirection == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT)
                 ? edgePatternExpr.getRightVertex() : edgePatternExpr.getLeftVertex();
-        VariableExpr destVariableExpr = new VariableExpr(destVertexExpr.getVariableExpr().getVar());
         LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
-        FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, destVariableExpr);
+        FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, destVertexExpr.getVariableExpr());
         schemaRecord.getFbList().add(fieldBinding);
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDetailPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDetailPrepare.java
index 6efd635..a507bd4 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDetailPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDetailPrepare.java
@@ -25,10 +25,9 @@
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.rewrite.util.LowerRewritingUtil;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.FieldBinding;
 import org.apache.asterix.lang.common.expression.ListConstructor;
@@ -49,7 +48,6 @@
             return;
         }
         EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
 
         // Insert our detail record into our schema.
         RecordConstructor detailRecord = new RecordConstructor(new ArrayList<>());
@@ -66,7 +64,7 @@
         edgeDirectionPrepare.transformRecord(detailRecord, inputExpr, sourceExpr);
 
         // Insert our source-key into our detail record.
-        GraphElementIdentifier edgeIdentifier = edgeDescriptor.generateIdentifiers(graphIdentifier).get(0);
+        EdgeIdentifier edgeIdentifier = edgePatternExpr.generateIdentifiers(graphIdentifier).get(0);
         List<List<String>> edgeSourceKey = elementLookupTable.getEdgeSourceKey(edgeIdentifier);
         List<Expression> sourceKeyExprList = LowerRewritingUtil.buildAccessorList(sourceExpr, edgeSourceKey).stream()
                 .map(e -> (Expression) e).collect(Collectors.toList());
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDirectionPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDirectionPrepare.java
index 4a9a216..df2d898 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDirectionPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDirectionPrepare.java
@@ -38,7 +38,7 @@
         EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
         EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
         EdgeDescriptor.EdgeDirection edgeDirection = edgeDescriptor.getEdgeDirection();
-        LiteralExpr fieldValueExpr = new LiteralExpr(new StringLiteral(edgeDirection.toString()));
+        LiteralExpr fieldValueExpr = new LiteralExpr(new StringLiteral(edgeDirection.name()));
         LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
         FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, fieldValueExpr);
         schemaRecord.getFbList().add(fieldBinding);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeSourceVertexPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeSourceVertexPrepare.java
index 5cc7bba..043f55a 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeSourceVertexPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeSourceVertexPrepare.java
@@ -42,9 +42,9 @@
         EdgeDescriptor.EdgeDirection edgeDirection = edgeDescriptor.getEdgeDirection();
         VertexPatternExpr sourceVertexExpr = (edgeDirection == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT)
                 ? edgePatternExpr.getLeftVertex() : edgePatternExpr.getRightVertex();
-        VariableExpr sourceVariableExpr = new VariableExpr(sourceVertexExpr.getVariableExpr().getVar());
+        VariableExpr sourceVariableExprCopy = new VariableExpr(sourceVertexExpr.getVariableExpr().getVar());
         LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
-        FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, sourceVariableExpr);
+        FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, sourceVariableExprCopy);
         schemaRecord.getFbList().add(fieldBinding);
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
index 6c4c2b1..8f67fa5 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
@@ -20,7 +20,7 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
 import org.apache.asterix.lang.common.base.Expression;
 
 @FunctionalInterface
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/VertexDetailPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/VertexDetailPrepare.java
index c6cde11..d9c583f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/VertexDetailPrepare.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/VertexDetailPrepare.java
@@ -25,9 +25,9 @@
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
+import org.apache.asterix.graphix.lang.rewrite.util.LowerRewritingUtil;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.FieldBinding;
 import org.apache.asterix.lang.common.expression.ListConstructor;
@@ -59,7 +59,7 @@
         elementLabelPrepare.transformRecord(detailRecord, inputExpr, sourceExpr);
 
         // Insert our vertex-key into our detail record.
-        GraphElementIdentifier vertexIdentifier = vertexPatternExpr.generateIdentifiers(graphIdentifier).get(0);
+        VertexIdentifier vertexIdentifier = vertexPatternExpr.generateIdentifiers(graphIdentifier).get(0);
         List<List<String>> vertexKey = elementLookupTable.getVertexKey(vertexIdentifier);
         List<Expression> vertexKeyExprList = LowerRewritingUtil.buildAccessorList(sourceExpr, vertexKey).stream()
                 .map(e -> (Expression) e).collect(Collectors.toList());
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/IFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/IFunctionRewrite.java
index 54b1963..cce1ec0 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/IFunctionRewrite.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/IFunctionRewrite.java
@@ -20,7 +20,7 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.function.GraphixFunctionMap;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesRewrite.java
index 3a1cca0..ad4a39c 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesRewrite.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesRewrite.java
@@ -21,8 +21,8 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.type.MaterializePathTypeComputer;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
@@ -36,7 +36,7 @@
             throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
                     GraphixFunctionIdentifiers.PATH_EDGES.toString());
         }
-        Identifier pathEdgeIdentifier = new Identifier(PathPatternAction.PATH_EDGES_FIELD_NAME);
+        Identifier pathEdgeIdentifier = new Identifier(MaterializePathTypeComputer.EDGES_FIELD_NAME);
         FieldAccessor pathEdgeAccess = new FieldAccessor(callExpr.getExprList().get(0), pathEdgeIdentifier);
         pathEdgeAccess.setSourceLocation(callExpr.getSourceLocation());
         return pathEdgeAccess;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountRewrite.java
index 32ebd92..e6f3f50 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountRewrite.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountRewrite.java
@@ -25,8 +25,8 @@
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.type.MaterializePathTypeComputer;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
@@ -44,7 +44,7 @@
 
         // Access the edges in our path.
         List<Expression> countFunctionArguments = new ArrayList<>();
-        Identifier pathEdgeIdentifier = new Identifier(PathPatternAction.PATH_EDGES_FIELD_NAME);
+        Identifier pathEdgeIdentifier = new Identifier(MaterializePathTypeComputer.EDGES_FIELD_NAME);
         FieldAccessor pathEdgeAccess = new FieldAccessor(callExpr.getExprList().get(0), pathEdgeIdentifier);
         pathEdgeAccess.setSourceLocation(callExpr.getSourceLocation());
         countFunctionArguments.add(pathEdgeAccess);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesRewrite.java
index 1dbc027..c9f5d1e 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesRewrite.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesRewrite.java
@@ -21,8 +21,8 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.type.MaterializePathTypeComputer;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
@@ -36,7 +36,7 @@
             throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
                     GraphixFunctionIdentifiers.PATH_VERTICES.toString());
         }
-        Identifier pathVertexIdentifier = new Identifier(PathPatternAction.PATH_VERTICES_FIELD_NAME);
+        Identifier pathVertexIdentifier = new Identifier(MaterializePathTypeComputer.VERTICES_FIELD_NAME);
         FieldAccessor pathVertexAccess = new FieldAccessor(callExpr.getExprList().get(0), pathVertexIdentifier);
         pathVertexAccess.setSourceLocation(callExpr.getSourceLocation());
         return pathVertexAccess;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/SchemaAccessRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/SchemaAccessRewrite.java
index 73fb2a6..639278b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/SchemaAccessRewrite.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/SchemaAccessRewrite.java
@@ -21,7 +21,7 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.graphix.function.prepare.AbstractElementPrepare;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/ElementEvaluationAnnotation.java
similarity index 60%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/ElementEvaluationAnnotation.java
index ff00077..9dfa97f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/ElementEvaluationAnnotation.java
@@ -16,16 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.lang.annotation;
 
-import java.io.PrintWriter;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+/**
+ * Annotation used to attach an evaluation approach to a graph element.
+ */
+public class ElementEvaluationAnnotation implements IExpressionAnnotation {
+    private final Kind kind;
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
-    @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    public ElementEvaluationAnnotation(Kind kind) {
+        this.kind = kind;
+    }
+
+    public Kind getKind() {
+        return kind;
+    }
+
+    public enum Kind {
+        EXPAND_AND_UNION,
+        SWITCH_AND_CYCLE
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/LoweringExemptAnnotation.java
similarity index 63%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/LoweringExemptAnnotation.java
index 4509792..3f59a72 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/LoweringExemptAnnotation.java
@@ -16,11 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.lang.annotation;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
-@FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+/**
+ * Annotation used to indicate that a VERTEX-PATTERN-EXPR or an EDGE-PATTERN-EXPR should not be lowered.
+ */
+public class LoweringExemptAnnotation implements IExpressionAnnotation {
+    public static final LoweringExemptAnnotation INSTANCE = new LoweringExemptAnnotation();
+
+    private LoweringExemptAnnotation() {
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/SubqueryVertexJoinAnnotation.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/SubqueryVertexJoinAnnotation.java
new file mode 100644
index 0000000..8425b14
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/SubqueryVertexJoinAnnotation.java
@@ -0,0 +1,38 @@
+/*
+ * 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.asterix.graphix.lang.annotation;
+
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
+
+/**
+ * Annotation used to indicate that a VERTEX-PATTERN-EXPR has been rewritten to JOIN with another VERTEX-PATTERN-EXPR
+ * in a non-local scope.
+ */
+public class SubqueryVertexJoinAnnotation implements IExpressionAnnotation {
+    private final VariableExpr sourceVertexVariable;
+
+    public SubqueryVertexJoinAnnotation(VariableExpr sourceVertexVariable) {
+        this.sourceVertexVariable = sourceVertexVariable;
+    }
+
+    public VariableExpr getSourceVertexVariable() {
+        return sourceVertexVariable;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrLetClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrLetClause.java
deleted file mode 100644
index 1a80aff..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrLetClause.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.asterix.graphix.lang.clause;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ILetCorrelateClauseVisitor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-
-/**
- * Clause for introducing a {@link LetClause} into the scope of any correlated clauses. This clause allows us to avoid
- * nesting the Graphix lowering result to handle any correlated clauses on graph elements.
- */
-public class CorrLetClause extends AbstractBinaryCorrelateClause {
-    private final LetClause letClause;
-
-    public CorrLetClause(Expression rightExpr, VariableExpr rightVar, VariableExpr rightPosVar) {
-        super(rightExpr, rightVar, rightPosVar);
-        letClause = new LetClause((rightVar == null) ? rightPosVar : rightVar, rightExpr);
-    }
-
-    @Override
-    public void setRightExpression(Expression rightExpr) {
-        VariableExpr variableExpr = (getRightVariable() == null) ? getPositionalVariable() : getRightVariable();
-        letClause.setVarExpr(variableExpr);
-        letClause.setBindingExpr(rightExpr);
-        super.setRightExpression(rightExpr);
-    }
-
-    @Override
-    public ClauseType getClauseType() {
-        return ClauseType.LET_CLAUSE;
-    }
-
-    @Override
-    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        if (visitor instanceof ILetCorrelateClauseVisitor) {
-            return ((ILetCorrelateClauseVisitor<R, T>) visitor).visit(this, arg);
-
-        } else {
-            // This node will survive our Graphix lowering, so by default we call the dispatch on our LET-CLAUSE node.
-            return letClause.accept(visitor, arg);
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrWhereClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrWhereClause.java
deleted file mode 100644
index 98ad42a..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrWhereClause.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.asterix.graphix.lang.clause;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.WhereClause;
-import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-
-/**
- * Clause for introducing a {@link WhereClause} in an intermediate list of correlated clauses. This clause allows us to
- * perform predicate push-down at the AST level (rather than at the Algebricks level).
- */
-public class CorrWhereClause extends AbstractBinaryCorrelateClause {
-    private final WhereClause whereClause;
-
-    public CorrWhereClause(Expression conditionExpr) {
-        super(conditionExpr, null, null);
-        whereClause = new WhereClause(conditionExpr);
-    }
-
-    public Expression getExpression() {
-        return whereClause.getWhereExpr();
-    }
-
-    public void setExpression(Expression expression) {
-        whereClause.setWhereExpr(expression);
-    }
-
-    @Override
-    public ClauseType getClauseType() {
-        return ClauseType.WHERE_CLAUSE;
-    }
-
-    @Override
-    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        // This node will survive our Graphix lowering, so by default we call the dispatch on our WHERE-CLAUSE node.
-        return whereClause.accept(visitor, arg);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java
index d8d4cad..c4455d5 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java
@@ -18,45 +18,58 @@
  */
 package org.apache.asterix.graphix.lang.clause;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
-import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
+import org.apache.asterix.lang.common.base.AbstractExtensionClause;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+import org.apache.asterix.metadata.declared.MetadataProvider;
 
 /**
- * The logical starting AST node for Graphix queries. A FROM-GRAPH node includes the following:
- * - Either a {@link GraphConstructor} OR a [dataverse, graph name] pair. The former indicates that we are dealing with
- * an anonymous graph, while the latter indicates that we must search our metadata for the graph.
- * - A list of {@link MatchClause} nodes, with a minimum size of one. The first MATCH node type must always be LEADING.
- * - A list of {@link AbstractBinaryCorrelateClause} nodes, which may be empty. These include UNNEST and explicit JOINs.
+ * The logical starting AST node for Graphix queries. Lowering a Graphix AST involves setting the
+ * {@link AbstractExtensionClause}, initially set to null. A FROM-GRAPH node includes the following:
+ * <ul>
+ *  <li>Either a {@link GraphConstructor} OR a [dataverse, graph name] pair. The former indicates that we are dealing
+ *  with an anonymous graph, while the latter indicates that we must search our metadata for the graph.</li>
+ *  <li>A list of {@link MatchClause} nodes, with a minimum size of one. The first MATCH node type must always be
+ *  LEADING.</li>
+ *  <li>A list of {@link AbstractBinaryCorrelateClause} nodes, which may be empty. These include UNNEST and explicit
+ *  JOINs.</li>
+ * </ul>
  */
-public class FromGraphClause extends AbstractClause {
-    // A FROM-MATCH must either have a graph constructor...
+public class FromGraphClause extends FromClause {
+    // A non-lowered FROM-GRAPH-CLAUSE must either have a graph constructor...
     private final GraphConstructor graphConstructor;
 
     // Or a reference to a named graph (both cannot be specified).
     private final DataverseName dataverse;
     private final Identifier name;
 
-    // Every FROM-MATCH **MUST** include at-least a single MATCH clause. Correlated clauses are optional.
+    // Every non-lowered FROM-GRAPH-CLAUSE **MUST** include at-least a single MATCH clause.
     private final List<MatchClause> matchClauses;
     private final List<AbstractBinaryCorrelateClause> correlateClauses;
 
+    // After lowering, we should have built an extension clause of some sort.
+    private AbstractExtensionClause lowerClause = null;
+
     public FromGraphClause(DataverseName dataverse, Identifier name, List<MatchClause> matchClauses,
             List<AbstractBinaryCorrelateClause> correlateClauses) {
+        super(Collections.emptyList());
         this.graphConstructor = null;
         this.dataverse = dataverse;
         this.name = Objects.requireNonNull(name);
         this.matchClauses = Objects.requireNonNull(matchClauses);
         this.correlateClauses = Objects.requireNonNull(correlateClauses);
-
         if (matchClauses.isEmpty()) {
             throw new IllegalArgumentException("FROM-MATCH requires at least one MATCH clause.");
         }
@@ -64,17 +77,36 @@
 
     public FromGraphClause(GraphConstructor graphConstructor, List<MatchClause> matchClauses,
             List<AbstractBinaryCorrelateClause> correlateClauses) {
+        super(Collections.emptyList());
         this.graphConstructor = Objects.requireNonNull(graphConstructor);
         this.dataverse = null;
         this.name = null;
         this.matchClauses = Objects.requireNonNull(matchClauses);
         this.correlateClauses = Objects.requireNonNull(correlateClauses);
-
         if (matchClauses.isEmpty()) {
             throw new IllegalArgumentException("FROM-MATCH requires at least one MATCH clause.");
         }
     }
 
+    public FromGraphClause(AbstractExtensionClause lowerClause) {
+        super(Collections.emptyList());
+        this.lowerClause = Objects.requireNonNull(lowerClause);
+        this.graphConstructor = null;
+        this.dataverse = null;
+        this.name = null;
+        this.matchClauses = Collections.emptyList();
+        this.correlateClauses = Collections.emptyList();
+    }
+
+    public GraphIdentifier getGraphIdentifier(MetadataProvider metadataProvider) {
+        DataverseName dataverseName = metadataProvider.getDefaultDataverseName();
+        if (this.dataverse != null) {
+            dataverseName = this.dataverse;
+        }
+        return (graphConstructor == null) ? new GraphIdentifier(dataverseName, name.getValue())
+                : new GraphIdentifier(dataverseName, graphConstructor.getInstanceID());
+    }
+
     public GraphConstructor getGraphConstructor() {
         return graphConstructor;
     }
@@ -95,25 +127,47 @@
         return correlateClauses;
     }
 
-    @Override
-    public ClauseType getClauseType() {
-        return null;
+    public AbstractExtensionClause getLowerClause() {
+        return lowerClause;
+    }
+
+    public void setLowerClause(AbstractExtensionClause lowerClause) {
+        this.lowerClause = lowerClause;
     }
 
     @Override
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+        if (visitor instanceof IGraphixLangVisitor) {
+            return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+
+        } else if (lowerClause != null) {
+            return visitor.visit(lowerClause.getVisitorExtension(), arg);
+
+        } else {
+            return ((ISqlppVisitor<R, T>) visitor).visit(this, arg);
+        }
     }
 
     @Override
     public String toString() {
-        return (graphConstructor != null) ? graphConstructor.toString()
-                : ((dataverse == null) ? name.getValue() : (dataverse + "." + name));
+        if (lowerClause != null) {
+            return lowerClause.toString();
+
+        } else if (graphConstructor != null) {
+            return graphConstructor.toString();
+
+        } else if (dataverse != null && name != null) {
+            return dataverse + "." + name.getValue();
+
+        } else if (dataverse == null && name != null) {
+            return name.getValue();
+        }
+        throw new IllegalStateException();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(graphConstructor, dataverse, name, matchClauses, correlateClauses);
+        return Objects.hash(graphConstructor, dataverse, name, matchClauses, correlateClauses, lowerClause);
     }
 
     @Override
@@ -127,6 +181,7 @@
         FromGraphClause that = (FromGraphClause) object;
         return Objects.equals(graphConstructor, that.graphConstructor) && Objects.equals(dataverse, that.dataverse)
                 && Objects.equals(name, that.name) && matchClauses.equals(that.matchClauses)
-                && Objects.equals(correlateClauses, that.correlateClauses);
+                && Objects.equals(correlateClauses, that.correlateClauses)
+                && Objects.equals(lowerClause, that.lowerClause);
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/GraphSelectBlock.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/GraphSelectBlock.java
deleted file mode 100644
index 8871aaf..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/GraphSelectBlock.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.asterix.graphix.lang.clause;
-
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.clause.GroupbyClause;
-import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-import org.apache.asterix.lang.sqlpp.clause.SelectClause;
-import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
-
-/**
- * Starting AST node for a Graphix query, which will replace the FROM-CLAUSE with a {@link FromGraphClause} on
- * parse. The goal of our Graphix rewriter is to replace these {@link FromGraphClause} nodes with applicable
- * {@link org.apache.asterix.lang.sqlpp.clause.FromClause} nodes.
- */
-public class GraphSelectBlock extends SelectBlock {
-    private FromGraphClause fromGraphClause;
-
-    public GraphSelectBlock(SelectClause selectClause, FromGraphClause fromGraphClause,
-            List<AbstractClause> letWhereClauses, GroupbyClause groupbyClause,
-            List<AbstractClause> letHavingClausesAfterGby) {
-        super(selectClause, null, letWhereClauses, groupbyClause, letHavingClausesAfterGby);
-        this.fromGraphClause = fromGraphClause;
-    }
-
-    public FromGraphClause getFromGraphClause() {
-        return fromGraphClause;
-    }
-
-    public void setFromGraphClause(FromGraphClause fromGraphClause) {
-        this.fromGraphClause = fromGraphClause;
-    }
-
-    public boolean hasFromGraphClause() {
-        return fromGraphClause != null;
-    }
-
-    @Override
-    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        if (hasFromClause()) {
-            return ((ISqlppVisitor<R, T>) visitor).visit(this, arg);
-
-        } else {
-            return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(getFromClause(), getFromGraphClause(), getGroupbyClause(), getLetWhereList(),
-                getLetHavingListAfterGroupby(), getSelectClause());
-    }
-
-    @Override
-    public boolean equals(Object object) {
-        if (this == object) {
-            return true;
-        }
-        if (!(object instanceof GraphSelectBlock)) {
-            return false;
-        }
-        GraphSelectBlock target = (GraphSelectBlock) object;
-        return super.equals(target) && Objects.equals(getFromGraphClause(), target.getFromGraphClause());
-    }
-
-    @Override
-    public String toString() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(getSelectClause());
-        if (hasFromClause()) {
-            sb.append(' ').append(getFromClause());
-        } else if (hasFromGraphClause()) {
-            sb.append(' ').append(getFromGraphClause());
-        }
-        if (hasLetWhereClauses()) {
-            sb.append(' ').append(getLetWhereList());
-        }
-        if (hasGroupbyClause()) {
-            sb.append(' ').append(getGroupbyClause());
-        }
-        if (hasLetHavingClausesAfterGroupby()) {
-            sb.append(' ').append(getLetHavingListAfterGroupby());
-        }
-        return sb.toString();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerListClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerListClause.java
new file mode 100644
index 0000000..18e85c9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerListClause.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.asterix.graphix.lang.clause;
+
+import java.util.Objects;
+
+import org.apache.asterix.graphix.lang.clause.extension.LowerListClauseExtension;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.lang.common.base.AbstractExtensionClause;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+
+/**
+ * A functional equivalent to the {@link FromTerm}, used as a container for lowering a non-recursive portion of a
+ * {@link FromGraphClause}. We also maintain a list of {@link LetClause} nodes that bind vertex, edge, and path
+ * variables to expressions after the main linked list.
+ */
+public class LowerListClause extends AbstractExtensionClause {
+    private final LowerListClauseExtension lowerClauseExtension;
+    private final ClauseCollection clauseCollection;
+
+    public LowerListClause(ClauseCollection clauseCollection) {
+        this.clauseCollection = clauseCollection;
+        this.lowerClauseExtension = new LowerListClauseExtension(this);
+    }
+
+    public ClauseCollection getClauseCollection() {
+        return clauseCollection;
+    }
+
+    @Override
+    public IVisitorExtension getVisitorExtension() {
+        return lowerClauseExtension;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(clauseCollection.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LowerListClause)) {
+            return false;
+        }
+        LowerListClause that = (LowerListClause) object;
+        return Objects.equals(this.clauseCollection, that.clauseCollection);
+    }
+
+    @Override
+    public String toString() {
+        return clauseCollection.toString();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerSwitchClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerSwitchClause.java
new file mode 100644
index 0000000..99d8610
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/LowerSwitchClause.java
@@ -0,0 +1,238 @@
+/*
+ * 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.asterix.graphix.lang.clause;
+
+import java.util.Objects;
+
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsNavigationOption;
+import org.apache.asterix.graphix.lang.clause.extension.LowerSwitchClauseExtension;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.MatchSemanticAction;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.CollectionTable;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractExtensionClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
+import org.apache.asterix.lang.common.expression.IndexAccessor;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.IntegerLiteral;
+
+/**
+ * A functional equivalent to the {@link org.apache.asterix.lang.sqlpp.clause.JoinClause}, used as a container for
+ * lowering a recursive / ambiguous portion of a {@link FromGraphClause}.
+ */
+public class LowerSwitchClause extends AbstractExtensionClause {
+    private final LowerSwitchClauseExtension lowerClauseExtension;
+    private final ClauseOutputEnvironment clauseOutputEnvironment;
+    private final ClauseInputEnvironment clauseInputEnvironment;
+    private final CollectionTable collectionLookupTable;
+
+    /**
+     * The following is set by {@link MatchSemanticAction}.
+     */
+    private SemanticsNavigationOption navigationSemantics;
+
+    /**
+     * The output to a BFS clause will be of type list, containing three items.
+     */
+    public static class ClauseOutputEnvironment {
+        public static final int OUTPUT_VERTEX_ITERATION_VARIABLE_INDEX = 0;
+        public static final int OUTPUT_VERTEX_JOIN_VARIABLE_INDEX = 1;
+        public static final int OUTPUT_PATH_VARIABLE_INDEX = 2;
+        private final VariableExpr outputVariable;
+        private final ElementLabel endingLabel;
+
+        // We provide the following as output, through our output variable.
+        private final VariableExpr outputVertexIterationVariable;
+        private final VariableExpr outputVertexJoinVariable;
+        private final VariableExpr pathVariable;
+
+        public ClauseOutputEnvironment(VariableExpr outputVariable, VariableExpr outputVertexIterationVariable,
+                VariableExpr outputVertexJoinVariable, VariableExpr pathVariable, ElementLabel endingLabel) {
+            this.outputVariable = Objects.requireNonNull(outputVariable);
+            this.outputVertexIterationVariable = Objects.requireNonNull(outputVertexIterationVariable);
+            this.outputVertexJoinVariable = Objects.requireNonNull(outputVertexJoinVariable);
+            this.pathVariable = Objects.requireNonNull(pathVariable);
+            this.endingLabel = endingLabel;
+        }
+
+        public VariableExpr getOutputVariable() {
+            return outputVariable;
+        }
+
+        public VariableExpr getOutputVertexIterationVariable() {
+            return outputVertexIterationVariable;
+        }
+
+        public VariableExpr getOutputVertexJoinVariable() {
+            return outputVertexJoinVariable;
+        }
+
+        public VariableExpr getPathVariable() {
+            return pathVariable;
+        }
+
+        public ElementLabel getEndingLabel() {
+            return endingLabel;
+        }
+
+        public Expression buildIterationVariableAccess() {
+            LiteralExpr indexExpr = new LiteralExpr(new IntegerLiteral(OUTPUT_VERTEX_ITERATION_VARIABLE_INDEX));
+            return new IndexAccessor(outputVariable, IndexAccessor.IndexKind.ELEMENT, indexExpr);
+        }
+
+        public Expression buildJoinVariableAccess() {
+            LiteralExpr indexExpr = new LiteralExpr(new IntegerLiteral(OUTPUT_VERTEX_JOIN_VARIABLE_INDEX));
+            return new IndexAccessor(outputVariable, IndexAccessor.IndexKind.ELEMENT, indexExpr);
+        }
+
+        public Expression buildPathVariableAccess() {
+            LiteralExpr indexExpr = new LiteralExpr(new IntegerLiteral(OUTPUT_PATH_VARIABLE_INDEX));
+            return new IndexAccessor(outputVariable, IndexAccessor.IndexKind.ELEMENT, indexExpr);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(outputVariable, outputVertexIterationVariable, outputVertexJoinVariable, pathVariable);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (!(object instanceof ClauseOutputEnvironment)) {
+                return false;
+            }
+            ClauseOutputEnvironment that = (ClauseOutputEnvironment) object;
+            return Objects.equals(this.outputVariable, that.outputVariable)
+                    && Objects.equals(this.outputVertexIterationVariable, that.outputVertexIterationVariable)
+                    && Objects.equals(this.outputVertexJoinVariable, that.outputVertexJoinVariable)
+                    && Objects.equals(this.pathVariable, that.pathVariable)
+                    && Objects.equals(this.endingLabel, that.endingLabel);
+        }
+
+        @Override
+        public String toString() {
+            String exposeToOutputString = "clause-output-env (" + outputVariable.toString() + ")";
+            String iterationString = "iteration: " + outputVertexIterationVariable.toString();
+            String joinString = "join: " + outputVertexJoinVariable.toString();
+            String pathString = "path: " + pathVariable.toString();
+            return String.format("%s: {%s, %s, %s}", exposeToOutputString, iterationString, joinString, pathString);
+        }
+    }
+
+    /**
+     * The input to a BFS clause will be a single representative vertex.
+     */
+    public static class ClauseInputEnvironment {
+        private final VariableExpr inputVariable;
+        private final ElementLabel startingLabel;
+
+        public ClauseInputEnvironment(VariableExpr inputVariable, ElementLabel startingLabel) {
+            this.inputVariable = inputVariable;
+            this.startingLabel = startingLabel;
+        }
+
+        public VariableExpr getInputVariable() {
+            return inputVariable;
+        }
+
+        public ElementLabel getStartingLabel() {
+            return startingLabel;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(inputVariable, startingLabel);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (!(object instanceof ClauseInputEnvironment)) {
+                return false;
+            }
+            ClauseInputEnvironment that = (ClauseInputEnvironment) object;
+            return Objects.equals(this.inputVariable, that.inputVariable)
+                    && Objects.equals(this.startingLabel, that.startingLabel);
+        }
+    }
+
+    public LowerSwitchClause(CollectionTable pathClauseCollectionTable, ClauseInputEnvironment inputEnvironment,
+            ClauseOutputEnvironment outputEnvironment) {
+        this.collectionLookupTable = pathClauseCollectionTable;
+        this.clauseInputEnvironment = inputEnvironment;
+        this.clauseOutputEnvironment = outputEnvironment;
+        this.lowerClauseExtension = new LowerSwitchClauseExtension(this);
+    }
+
+    public void setNavigationSemantics(SemanticsNavigationOption navigationSemantics) {
+        this.navigationSemantics = navigationSemantics;
+    }
+
+    public SemanticsNavigationOption getNavigationSemantics() {
+        return navigationSemantics;
+    }
+
+    public ClauseInputEnvironment getClauseInputEnvironment() {
+        return clauseInputEnvironment;
+    }
+
+    public ClauseOutputEnvironment getClauseOutputEnvironment() {
+        return clauseOutputEnvironment;
+    }
+
+    public CollectionTable getCollectionLookupTable() {
+        return collectionLookupTable;
+    }
+
+    @Override
+    public IVisitorExtension getVisitorExtension() {
+        return lowerClauseExtension;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(collectionLookupTable, clauseInputEnvironment, clauseOutputEnvironment,
+                navigationSemantics);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof LowerSwitchClause)) {
+            return false;
+        }
+        LowerSwitchClause that = (LowerSwitchClause) object;
+        return Objects.equals(this.collectionLookupTable, that.collectionLookupTable)
+                && Objects.equals(this.clauseOutputEnvironment, that.clauseOutputEnvironment)
+                && Objects.equals(this.clauseInputEnvironment, that.clauseInputEnvironment)
+                && Objects.equals(this.navigationSemantics, that.navigationSemantics);
+    }
+
+    @Override
+    public String toString() {
+        return clauseOutputEnvironment.toString();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java
index 11580e0..7823bcd 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java
@@ -25,17 +25,21 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.optype.MatchType;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.MatchSemanticAction;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 /**
  * Container for a collection of {@link PathPatternExpr} nodes.
- * - A MATCH node has three types: LEADING (indicating that this node is first), INNER (indicating that this node is not
- * first, but all patterns must be matched), and LEFTOUTER (indicating that this node is optionally matched).
- * - Under isomorphism semantics, two patterns in different MATCH nodes (one pattern in a LEADING MATCH node and
- * one pattern in an INNER MATCH node) are equivalent to two patterns in a single LEADING MATCH node. See
- * {@link org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction} for more detail.
+ * <ul>
+ *  <li>A MATCH node has three types: LEADING (indicating that this node is first), INNER (indicating that this node
+ *  is not first, but all patterns must be matched), and LEFTOUTER (indicating that this node is optionally
+ *  matched).</li>
+ *  <li>Under isomorphism semantics, two patterns in different MATCH nodes (one pattern in a LEADING MATCH node and
+ *  one pattern in an INNER MATCH node) are equivalent to two patterns in a single LEADING MATCH node. See
+ *  {@link MatchSemanticAction} for more detail</li>
+ * </ul>
  */
 public class MatchClause extends AbstractClause {
     private final List<PathPatternExpr> pathExpressions;
@@ -43,7 +47,7 @@
 
     public MatchClause(List<PathPatternExpr> pathExpressions, MatchType matchType) {
         this.pathExpressions = Objects.requireNonNull(pathExpressions);
-        this.matchType = matchType;
+        this.matchType = Objects.requireNonNull(matchType);
     }
 
     public List<PathPatternExpr> getPathExpressions() {
@@ -67,7 +71,6 @@
     @Override
     public String toString() {
         String pathString = pathExpressions.stream().map(PathPatternExpr::toString).collect(Collectors.joining("\n"));
-        return matchType.toString() + " " + pathString;
-
+        return matchType + " " + pathString;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
similarity index 73%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
index 4509792..ddc6d5f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
@@ -16,11 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.lang.clause.extension;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
 
-@FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+public interface IGraphixVisitorExtension extends IVisitorExtension {
+    Kind getKind();
+
+    enum Kind {
+        LOWER_LIST,
+        LOWER_SWITCH
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerListClauseExtension.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerListClauseExtension.java
new file mode 100644
index 0000000..caf8ff6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerListClauseExtension.java
@@ -0,0 +1,339 @@
+/*
+ * 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.asterix.graphix.lang.clause.extension;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.AbstractCallExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.parser.ScopeChecker;
+import org.apache.asterix.lang.common.rewrites.VariableSubstitutionEnvironment;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * @see LowerListClause
+ */
+public class LowerListClauseExtension implements IGraphixVisitorExtension {
+    private final ClauseCollection clauseCollection;
+    private final LowerListClause lowerListClause;
+
+    public LowerListClauseExtension(LowerListClause lowerListClause) {
+        this.clauseCollection = lowerListClause.getClauseCollection();
+        this.lowerListClause = lowerListClause;
+    }
+
+    public LowerListClause getLowerListClause() {
+        return lowerListClause;
+    }
+
+    @Override
+    public Expression simpleExpressionDispatch(ILangVisitor<Expression, ILangExpression> simpleExpressionVisitor,
+            ILangExpression argument) throws CompilationException {
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                simpleExpressionVisitor.visit(lowerSwitchClause.getVisitorExtension(), argument);
+
+            } else {
+                workingClause.accept(simpleExpressionVisitor, argument);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void freeVariableDispatch(ILangVisitor<Void, Collection<VariableExpr>> freeVariableVisitor,
+            Collection<VariableExpr> freeVariables) throws CompilationException {
+        Collection<VariableExpr> bindingVariables = new HashSet<>();
+        Collection<VariableExpr> clauseFreeVariables = new HashSet<>();
+        for (AbstractClause workingClause : clauseCollection) {
+            clauseFreeVariables.clear();
+            switch (workingClause.getClauseType()) {
+                case LET_CLAUSE:
+                    LetClause letClause = (LetClause) workingClause;
+                    letClause.getBindingExpr().accept(freeVariableVisitor, clauseFreeVariables);
+                    clauseFreeVariables.removeAll(bindingVariables);
+                    freeVariables.addAll(clauseFreeVariables);
+                    bindingVariables.add(letClause.getVarExpr());
+                    break;
+
+                case UNNEST_CLAUSE:
+                    UnnestClause unnestClause = (UnnestClause) workingClause;
+                    unnestClause.getRightExpression().accept(freeVariableVisitor, clauseFreeVariables);
+                    clauseFreeVariables.removeAll(bindingVariables);
+                    freeVariables.addAll(clauseFreeVariables);
+                    bindingVariables.add(unnestClause.getRightVariable());
+                    if (unnestClause.hasPositionalVariable()) {
+                        bindingVariables.add(unnestClause.getPositionalVariable());
+                    }
+                    break;
+
+                case JOIN_CLAUSE:
+                    JoinClause joinClause = (JoinClause) workingClause;
+                    joinClause.getRightExpression().accept(freeVariableVisitor, clauseFreeVariables);
+                    clauseFreeVariables.removeAll(bindingVariables);
+                    freeVariables.addAll(clauseFreeVariables);
+
+                    // Handle our condition expression, which can reference its right variable.
+                    Collection<VariableExpr> conditionFreeVariables = new HashSet<>();
+                    joinClause.getConditionExpression().accept(freeVariableVisitor, conditionFreeVariables);
+                    conditionFreeVariables.removeAll(bindingVariables);
+                    conditionFreeVariables.remove(joinClause.getRightVariable());
+                    bindingVariables.add(joinClause.getRightVariable());
+                    if (joinClause.hasPositionalVariable()) {
+                        conditionFreeVariables.remove(joinClause.getPositionalVariable());
+                        bindingVariables.add(joinClause.getPositionalVariable());
+                    }
+                    freeVariables.addAll(conditionFreeVariables);
+                    break;
+
+                case WHERE_CLAUSE:
+                    WhereClause whereClause = (WhereClause) workingClause;
+                    whereClause.getWhereExpr().accept(freeVariableVisitor, clauseFreeVariables);
+                    clauseFreeVariables.removeAll(bindingVariables);
+                    freeVariables.addAll(clauseFreeVariables);
+                    break;
+
+                case EXTENSION:
+                    if (workingClause instanceof LowerSwitchClause) {
+                        LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                        IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                        visitorExtension.freeVariableDispatch(freeVariableVisitor, freeVariables);
+                        break;
+                    }
+
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                            "Illegal clause found in the lower clause list!");
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void bindingVariableDispatch(ILangVisitor<Void, Collection<VariableExpr>> bindingVariableVisitor,
+            Collection<VariableExpr> bindingVariables) {
+        for (AbstractClause workingClause : clauseCollection) {
+            switch (workingClause.getClauseType()) {
+                case LET_CLAUSE:
+                    LetClause letClause = (LetClause) workingClause;
+                    bindingVariables.add(letClause.getVarExpr());
+                    break;
+
+                case UNNEST_CLAUSE:
+                case JOIN_CLAUSE:
+                    AbstractBinaryCorrelateClause correlateClause = (AbstractBinaryCorrelateClause) workingClause;
+                    bindingVariables.add(correlateClause.getRightVariable());
+                    break;
+
+                case EXTENSION:
+                    if (workingClause instanceof LowerSwitchClause) {
+                        LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                        bindingVariables.add(lowerSwitchClause.getClauseOutputEnvironment().getOutputVariable());
+                        break;
+                    }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression variableScopeDispatch(ILangVisitor<Expression, ILangExpression> scopingVisitor,
+            ILangExpression argument, ScopeChecker scopeChecker) throws CompilationException {
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                // We do not extend the scope for our LET-CLAUSE nodes.
+                LetClause letClause = (LetClause) workingClause;
+                letClause.setBindingExpr(letClause.getBindingExpr().accept(scopingVisitor, letClause));
+                VariableExpr letClauseVariable = letClause.getVarExpr();
+                if (scopeChecker.getCurrentScope().findLocalSymbol(letClauseVariable.getVar().getValue()) != null) {
+                    String varName = SqlppVariableUtil.toUserDefinedName(letClauseVariable.getVar().getValue());
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, letClauseVariable.getSourceLocation(),
+                            "Duplicate alias definitions: " + varName);
+                }
+                scopeChecker.getCurrentScope().addNewVarSymbolToScope(letClauseVariable.getVar(),
+                        Set.of(AbstractSqlppExpressionScopingVisitor.SqlppVariableAnnotation.CONTEXT_VARIABLE));
+
+            } else if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                visitorExtension.variableScopeDispatch(scopingVisitor, argument, scopeChecker);
+
+            } else {
+                workingClause.accept(scopingVisitor, argument);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public ILangExpression deepCopyDispatch(ILangVisitor<ILangExpression, Void> deepCopyVisitor)
+            throws CompilationException {
+        ClauseCollection copyCollection = new ClauseCollection(clauseCollection.getSourceLocation());
+        for (AbstractClause clause : clauseCollection.getNonRepresentativeClauses()) {
+            if (clause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) clause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                AbstractClause copiedClause = (AbstractClause) visitorExtension.deepCopyDispatch(deepCopyVisitor);
+                copyCollection.addNonRepresentativeClause(copiedClause);
+
+            } else {
+                copyCollection.addNonRepresentativeClause((AbstractClause) clause.accept(deepCopyVisitor, null));
+            }
+        }
+        for (LetClause clause : clauseCollection.getRepresentativeVertexBindings()) {
+            LetClause copiedClause = (LetClause) clause.accept(deepCopyVisitor, null);
+            copyCollection.addVertexBinding(copiedClause.getVarExpr(), copiedClause.getBindingExpr());
+        }
+        for (LetClause clause : clauseCollection.getRepresentativeEdgeBindings()) {
+            LetClause copiedClause = (LetClause) clause.accept(deepCopyVisitor, null);
+            copyCollection.addEdgeBinding(copiedClause.getVarExpr(), copiedClause.getBindingExpr());
+        }
+        for (LetClause clause : clauseCollection.getRepresentativePathBindings()) {
+            LetClause copiedClause = (LetClause) clause.accept(deepCopyVisitor, null);
+            copyCollection.addPathBinding(copiedClause.getVarExpr(), copiedClause.getBindingExpr());
+        }
+        for (AbstractBinaryCorrelateClause clause : clauseCollection.getUserDefinedCorrelateClauses()) {
+            AbstractBinaryCorrelateClause copiedClause =
+                    (AbstractBinaryCorrelateClause) clause.accept(deepCopyVisitor, null);
+            copyCollection.addUserDefinedCorrelateClause(copiedClause);
+        }
+
+        // Note: a LOWER-LIST-CLAUSE is also the entry-point for a FROM-GRAPH-CLAUSE, so we return the latter.
+        LowerListClause copyListClause = new LowerListClause(copyCollection);
+        copyListClause.setSourceLocation(lowerListClause.getSourceLocation());
+        FromGraphClause fromGraphClause = new FromGraphClause(copyListClause);
+        fromGraphClause.setSourceLocation(lowerListClause.getSourceLocation());
+        return fromGraphClause;
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> remapCloneDispatch(
+            ILangVisitor<Pair<ILangExpression, VariableSubstitutionEnvironment>, VariableSubstitutionEnvironment> remapCloneVisitor,
+            VariableSubstitutionEnvironment substitutionEnvironment) {
+        // TODO (GLENN): Finish the remap-clone dispatch.
+        return null;
+    }
+
+    @Override
+    public Boolean inlineUDFsDispatch(ILangVisitor<Boolean, Void> inlineUDFsVisitor) throws CompilationException {
+        boolean changed = false;
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                changed |= visitorExtension.inlineUDFsDispatch(inlineUDFsVisitor);
+
+            } else {
+                changed |= workingClause.accept(inlineUDFsVisitor, null);
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public Void gatherFunctionsDispatch(ILangVisitor<Void, Void> gatherFunctionsVisitor,
+            Collection<? super AbstractCallExpression> functionCalls) throws CompilationException {
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                visitorExtension.gatherFunctionsDispatch(gatherFunctionsVisitor, functionCalls);
+
+            } else {
+                workingClause.accept(gatherFunctionsVisitor, null);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Boolean checkSubqueryDispatch(ILangVisitor<Boolean, ILangExpression> checkSubqueryVisitor,
+            ILangExpression argument) throws CompilationException {
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                if (visitorExtension.checkSubqueryDispatch(checkSubqueryVisitor, argument)) {
+                    return true;
+                }
+
+            } else if (workingClause.accept(checkSubqueryVisitor, null)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Boolean check92AggregateDispatch(ILangVisitor<Boolean, ILangExpression> check92AggregateVisitor,
+            ILangExpression argument) {
+        return false;
+    }
+
+    @Override
+    public Boolean checkNonFunctionalDispatch(ILangVisitor<Boolean, Void> checkNonFunctionalVisitor)
+            throws CompilationException {
+        for (AbstractClause workingClause : clauseCollection) {
+            if (workingClause instanceof LowerSwitchClause) {
+                LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                IVisitorExtension visitorExtension = lowerSwitchClause.getVisitorExtension();
+                if (visitorExtension.checkNonFunctionalDispatch(checkNonFunctionalVisitor)) {
+                    return true;
+                }
+
+            } else if (workingClause.accept(checkNonFunctionalVisitor, null)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Boolean checkDatasetOnlyDispatch(ILangVisitor<Boolean, VariableExpr> checkDatasetOnlyVisitor,
+            VariableExpr datasetCandidate) {
+        return false;
+    }
+
+    @Override
+    public Kind getKind() {
+        return Kind.LOWER_LIST;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerSwitchClauseExtension.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerSwitchClauseExtension.java
new file mode 100644
index 0000000..436933d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/LowerSwitchClauseExtension.java
@@ -0,0 +1,252 @@
+/*
+ * 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.asterix.graphix.lang.clause.extension;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.CollectionTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.StateContainer;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.context.Scope;
+import org.apache.asterix.lang.common.expression.AbstractCallExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.parser.ScopeChecker;
+import org.apache.asterix.lang.common.rewrites.VariableSubstitutionEnvironment;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * @see LowerSwitchClause
+ */
+public class LowerSwitchClauseExtension implements IGraphixVisitorExtension {
+    private final CollectionTable collectionLookupTable;
+    private final LowerSwitchClause lowerSwitchClause;
+
+    public LowerSwitchClauseExtension(LowerSwitchClause lowerSwitchClause) {
+        this.collectionLookupTable = lowerSwitchClause.getCollectionLookupTable();
+        this.lowerSwitchClause = lowerSwitchClause;
+    }
+
+    public LowerSwitchClause getLowerSwitchClause() {
+        return lowerSwitchClause;
+    }
+
+    @Override
+    public Expression simpleExpressionDispatch(ILangVisitor<Expression, ILangExpression> simpleExpressionVisitor,
+            ILangExpression argument) throws CompilationException {
+        for (ClauseCollection clauseCollection : collectionLookupTable) {
+            for (AbstractClause workingClause : clauseCollection) {
+                workingClause.accept(simpleExpressionVisitor, argument);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void freeVariableDispatch(ILangVisitor<Void, Collection<VariableExpr>> freeVariableVisitor,
+            Collection<VariableExpr> freeVariables) throws CompilationException {
+        Iterator<Pair<ElementLabel, List<CollectionTable.Entry>>> entryIterator = collectionLookupTable.entryIterator();
+        while (entryIterator.hasNext()) {
+            Pair<ElementLabel, List<CollectionTable.Entry>> tableEntry = entryIterator.next();
+            StateContainer inputState = collectionLookupTable.getInputMap().get(tableEntry.first);
+            for (CollectionTable.Entry entry : tableEntry.second) {
+                ClauseCollection clauseCollection = entry.getClauseCollection();
+                LowerListClause lowerListClause = new LowerListClause(clauseCollection);
+                LowerListClauseExtension lowerListClauseExtension = new LowerListClauseExtension(lowerListClause);
+
+                // The input variables to each branch are **not** free (in the context of this visitor).
+                Set<VariableExpr> clauseCollectionFreeVars = new HashSet<>();
+                lowerListClauseExtension.freeVariableDispatch(freeVariableVisitor, clauseCollectionFreeVars);
+                clauseCollectionFreeVars.removeIf(v -> {
+                    final VariableExpr inputJoinVariable = inputState.getJoinVariable();
+                    final VariableExpr inputIterationVariable = inputState.getIterationVariable();
+                    return v.equals(inputJoinVariable) || v.equals(inputIterationVariable);
+                });
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void bindingVariableDispatch(ILangVisitor<Void, Collection<VariableExpr>> bindingVariableVisitor,
+            Collection<VariableExpr> bindingVariables) throws CompilationException {
+        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, lowerSwitchClause.getSourceLocation(),
+                "Binding variable dispatch invoked for LOWER-SWITCH-CLAUSE!");
+    }
+
+    @Override
+    public Expression variableScopeDispatch(ILangVisitor<Expression, ILangExpression> scopingVisitor,
+            ILangExpression argument, ScopeChecker scopeChecker) throws CompilationException {
+        // Traverse our branches.
+        Iterator<Pair<ElementLabel, List<CollectionTable.Entry>>> entryIterator = collectionLookupTable.entryIterator();
+        while (entryIterator.hasNext()) {
+            Pair<ElementLabel, List<CollectionTable.Entry>> tableEntry = entryIterator.next();
+            StateContainer inputState = collectionLookupTable.getInputMap().get(tableEntry.first);
+            for (CollectionTable.Entry entry : tableEntry.second) {
+                ClauseCollection clauseCollection = entry.getClauseCollection();
+                LowerListClause lowerListClause = new LowerListClause(clauseCollection);
+                LowerListClauseExtension lowerListClauseExtension = new LowerListClauseExtension(lowerListClause);
+
+                // Our input variables should only be visible to each branch.
+                Scope newScope = scopeChecker.createNewScope();
+                addVariableToScope(newScope, inputState.getJoinVariable().getVar());
+                addVariableToScope(newScope, inputState.getIterationVariable().getVar());
+                lowerListClauseExtension.variableScopeDispatch(scopingVisitor, argument, scopeChecker);
+                scopeChecker.removeCurrentScope();
+            }
+        }
+
+        // Introduce our output variable into scope.
+        LowerSwitchClause.ClauseOutputEnvironment outputEnv = lowerSwitchClause.getClauseOutputEnvironment();
+        addVariableToScope(scopeChecker.getCurrentScope(), outputEnv.getOutputVariable().getVar());
+        return null;
+    }
+
+    @Override
+    public ILangExpression deepCopyDispatch(ILangVisitor<ILangExpression, Void> deepCopyVisitor)
+            throws CompilationException {
+        CollectionTable copyTable = new CollectionTable();
+        Iterator<Pair<ElementLabel, List<CollectionTable.Entry>>> entryIterator = collectionLookupTable.entryIterator();
+        while (entryIterator.hasNext()) {
+            Pair<ElementLabel, List<CollectionTable.Entry>> tableEntry = entryIterator.next();
+            for (CollectionTable.Entry entry : tableEntry.second) {
+                ClauseCollection clauseCollection = entry.getClauseCollection();
+                LowerListClauseExtension llce = new LowerListClauseExtension(new LowerListClause(clauseCollection));
+                FromGraphClause fromGraphClause = (FromGraphClause) llce.deepCopyDispatch(deepCopyVisitor);
+                LowerListClause lowerClauseCopy = (LowerListClause) fromGraphClause.getLowerClause();
+                copyTable.putCollection(tableEntry.first, entry.getEdgeLabel(), entry.getDestinationLabel(),
+                        lowerClauseCopy.getClauseCollection(), entry.getEdgeDirection());
+            }
+        }
+        copyTable.setInputMap(collectionLookupTable.getInputMap());
+        copyTable.setOutputMap(collectionLookupTable.getOutputMap());
+        LowerSwitchClause.ClauseOutputEnvironment outputEnv = lowerSwitchClause.getClauseOutputEnvironment();
+        VariableExpr outputVariableExpr = outputEnv.getOutputVariable();
+        VariableExpr copyOutputVariableExpr = (VariableExpr) outputVariableExpr.accept(deepCopyVisitor, null);
+        LowerSwitchClause.ClauseOutputEnvironment copyOutputEnv = new LowerSwitchClause.ClauseOutputEnvironment(
+                copyOutputVariableExpr, outputEnv.getOutputVertexIterationVariable(),
+                outputEnv.getOutputVertexJoinVariable(), outputEnv.getPathVariable(), outputEnv.getEndingLabel());
+        LowerSwitchClause.ClauseInputEnvironment inputEnv = lowerSwitchClause.getClauseInputEnvironment();
+        VariableExpr inputVariableExpr = inputEnv.getInputVariable();
+        VariableExpr copyInputVariableExpr = (VariableExpr) inputVariableExpr.accept(deepCopyVisitor, null);
+        LowerSwitchClause.ClauseInputEnvironment copyInputEnv =
+                new LowerSwitchClause.ClauseInputEnvironment(copyInputVariableExpr, inputEnv.getStartingLabel());
+        LowerSwitchClause copyLowerSwitchClause = new LowerSwitchClause(copyTable, copyInputEnv, copyOutputEnv);
+        copyLowerSwitchClause.setNavigationSemantics(lowerSwitchClause.getNavigationSemantics());
+        copyLowerSwitchClause.setSourceLocation(lowerSwitchClause.getSourceLocation());
+        return copyLowerSwitchClause;
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> remapCloneDispatch(
+            ILangVisitor<Pair<ILangExpression, VariableSubstitutionEnvironment>, VariableSubstitutionEnvironment> remapCloneVisitor,
+            VariableSubstitutionEnvironment substitutionEnvironment) {
+        // TODO (GLENN): Finish the remap-clone dispatch.
+        return null;
+    }
+
+    @Override
+    public Boolean inlineUDFsDispatch(ILangVisitor<Boolean, Void> inlineUDFsVisitor) throws CompilationException {
+        boolean changed = false;
+        for (ClauseCollection clauseCollection : collectionLookupTable) {
+            for (AbstractClause workingClause : clauseCollection) {
+                changed |= workingClause.accept(inlineUDFsVisitor, null);
+            }
+        }
+        return changed;
+    }
+
+    @Override
+    public Void gatherFunctionsDispatch(ILangVisitor<Void, Void> gatherFunctionsVisitor,
+            Collection<? super AbstractCallExpression> functionCalls) throws CompilationException {
+        for (ClauseCollection clauseCollection : collectionLookupTable) {
+            for (AbstractClause workingClause : clauseCollection) {
+                workingClause.accept(gatherFunctionsVisitor, null);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Boolean checkSubqueryDispatch(ILangVisitor<Boolean, ILangExpression> checkSubqueryVisitor,
+            ILangExpression argument) throws CompilationException {
+        for (ClauseCollection clauseCollection : collectionLookupTable) {
+            for (AbstractClause workingClause : clauseCollection) {
+                if (workingClause.accept(checkSubqueryVisitor, null)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Boolean check92AggregateDispatch(ILangVisitor<Boolean, ILangExpression> check92AggregateVisitor,
+            ILangExpression argument) {
+        return false;
+    }
+
+    @Override
+    public Boolean checkNonFunctionalDispatch(ILangVisitor<Boolean, Void> checkNonFunctionalVisitor)
+            throws CompilationException {
+        for (ClauseCollection clauseCollection : collectionLookupTable) {
+            for (AbstractClause workingClause : clauseCollection) {
+                if (workingClause.accept(checkNonFunctionalVisitor, null)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Boolean checkDatasetOnlyDispatch(ILangVisitor<Boolean, VariableExpr> checkDatasetOnlyVisitor,
+            VariableExpr datasetCandidate) {
+        return false;
+    }
+
+    @Override
+    public Kind getKind() {
+        return Kind.LOWER_SWITCH;
+    }
+
+    private void addVariableToScope(Scope scope, VarIdentifier varIdentifier) throws CompilationException {
+        if (scope.findLocalSymbol(varIdentifier.getValue()) != null) {
+            String varName = SqlppVariableUtil.toUserDefinedName(varIdentifier.getValue());
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, lowerSwitchClause.getSourceLocation(),
+                    "Duplicate alias definitions: " + varName);
+        }
+        scope.addNewVarSymbolToScope(varIdentifier, Set.of());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/EdgePatternExpr.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/EdgePatternExpr.java
index 666141b..03d3bce 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/EdgePatternExpr.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/EdgePatternExpr.java
@@ -24,33 +24,39 @@
 import java.util.Objects;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractExpression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 /**
- * A query edge (not to be confused with an edge constructor) is composed of a {@link EdgeDescriptor} (containing the
- * edge labels, an optional edge variable, and the hop range), an list of optional internal {@link VertexPatternExpr}
- * instances, a left {@link VertexPatternExpr}, and a right {@link VertexPatternExpr}.
+ * A query edge (not to be confused with an edge constructor) is composed of:
+ * <ul>
+ *  <li>A {@link EdgeDescriptor} (containing the edge labels, an optional edge variable, and the hop range).</li>
+ *  <li>An optional internal {@link VertexPatternExpr}.</li>
+ *  <li>A left {@link VertexPatternExpr}.</li>
+ *  <li>A right {@link VertexPatternExpr}.</li>
+ * </ul>
  */
 public class EdgePatternExpr extends AbstractExpression {
-    private final List<VertexPatternExpr> internalVertices;
     private final EdgeDescriptor edgeDescriptor;
     private VertexPatternExpr leftVertex;
     private VertexPatternExpr rightVertex;
+    private VertexPatternExpr internalVertex;
 
     public EdgePatternExpr(VertexPatternExpr leftVertex, VertexPatternExpr rightVertex, EdgeDescriptor edgeDescriptor) {
         this.leftVertex = Objects.requireNonNull(leftVertex);
         this.rightVertex = Objects.requireNonNull(rightVertex);
         this.edgeDescriptor = Objects.requireNonNull(edgeDescriptor);
-        this.internalVertices = new ArrayList<>();
-
         if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH) {
             // If we have a sub-path, we have an internal vertex that we need to manage.
-            for (int i = 0; i < edgeDescriptor.getMaximumHops() - 1; i++) {
-                this.internalVertices.add(new VertexPatternExpr(null, new HashSet<>()));
-            }
+            this.internalVertex = new VertexPatternExpr(null, null, new HashSet<>());
+
+        } else {
+            this.internalVertex = null;
         }
     }
 
@@ -62,12 +68,12 @@
         return rightVertex;
     }
 
-    public EdgeDescriptor getEdgeDescriptor() {
-        return edgeDescriptor;
+    public VertexPatternExpr getInternalVertex() {
+        return internalVertex;
     }
 
-    public List<VertexPatternExpr> getInternalVertices() {
-        return internalVertices;
+    public EdgeDescriptor getEdgeDescriptor() {
+        return edgeDescriptor;
     }
 
     public void setLeftVertex(VertexPatternExpr leftVertex) {
@@ -78,14 +84,30 @@
         this.rightVertex = rightVertex;
     }
 
-    public void replaceInternalVertices(List<VertexPatternExpr> internalVertices) {
-        this.internalVertices.clear();
-        this.internalVertices.addAll(internalVertices);
+    public void setInternalVertex(VertexPatternExpr internalVertex) {
+        this.internalVertex = internalVertex;
+    }
+
+    public List<EdgeIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
+        List<EdgeIdentifier> edgeIdentifiers = new ArrayList<>();
+        for (ElementLabel leftLabel : leftVertex.getLabels()) {
+            for (ElementLabel rightLabel : rightVertex.getLabels()) {
+                for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT) {
+                        edgeIdentifiers.add(new EdgeIdentifier(graphIdentifier, leftLabel, edgeLabel, rightLabel));
+                    }
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+                        edgeIdentifiers.add(new EdgeIdentifier(graphIdentifier, rightLabel, edgeLabel, leftLabel));
+                    }
+                }
+            }
+        }
+        return edgeIdentifiers;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(leftVertex, rightVertex, edgeDescriptor, internalVertices);
+        return Objects.hash(leftVertex, rightVertex, internalVertex, edgeDescriptor);
     }
 
     @Override
@@ -98,8 +120,8 @@
         }
         EdgePatternExpr that = (EdgePatternExpr) object;
         return Objects.equals(this.leftVertex, that.leftVertex) && Objects.equals(this.rightVertex, that.rightVertex)
-                && Objects.equals(this.edgeDescriptor, that.edgeDescriptor)
-                && Objects.equals(this.internalVertices, that.internalVertices);
+                && Objects.equals(this.internalVertex, that.internalVertex)
+                && Objects.equals(this.edgeDescriptor, that.edgeDescriptor);
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphConstructor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphConstructor.java
index 245be24..e5b10eb 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphConstructor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphConstructor.java
@@ -23,16 +23,19 @@
 import java.util.UUID;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractExpression;
 import org.apache.asterix.lang.common.base.AbstractLangExpression;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 /**
- * An expression which describes the schema of a graph, containing a list of vertices ({@link VertexConstructor}) and
- * a list of edges ({@link EdgeConstructor}) that connect the aforementioned vertices.
+ * An expression which describes the schema of a graph, containing:
+ * <ul>
+ *  <li>A list of vertices ({@link VertexConstructor}).</li>
+ *  <li>A list of edges ({@link EdgeConstructor}) that connect the aforementioned vertices.</li>
+ * </ul>
  */
 public class GraphConstructor extends AbstractExpression {
     private final List<VertexConstructor> vertexConstructors;
@@ -88,9 +91,11 @@
 
     /**
      * A vertex constructor (not be confused with a query vertex) is composed of the following:
-     * - An AST containing the vertex body expression, as well as the raw body string itself.
-     * - A single vertex label that uniquely identifies the vertex.
-     * - A list of primary key fields, used in the JOIN clause with edges.
+     * <ul>
+     *  <li>An AST containing the vertex body expression, as well as the raw body string itself.</li>
+     *  <li>A single vertex label that uniquely identifies the vertex.</li>
+     *  <li>A list of primary key fields, used in the JOIN clause with edges.</li>
+     * </ul>
      */
     public static class VertexConstructor extends AbstractLangExpression {
         private final List<Integer> primaryKeySourceIndicators;
@@ -160,12 +165,14 @@
 
     /**
      * An edge constructor (not be confused with a query edge) is composed of the following:
-     * - An AST containing the edge body expression, as well as the raw body string itself.
-     * - A single edge label that uniquely identifies the edge.
-     * - A single label that denotes the source vertices of this edge, as well as another label that denotes the
-     * destination vertices of this edge.
-     * - A list of source key fields, used in the JOIN clause with the corresponding source vertices.
-     * - A list of destination key fields, used in the JOIN clause with the corresponding destination vertices.
+     * <ul>
+     *  <li>An AST containing the edge body expression, as well as the raw body string itself.</li>
+     *  <li>A single edge label that uniquely identifies the edge.</li>
+     *  <li>A single label that denotes the source vertices of this edge, as well as another label that denotes the
+     *  destination vertices of this edge.</li>
+     *  <li>A list of source key fields, used in the JOIN clause with the corresponding source vertices.</li>
+     *  <li>A list of destination key fields, used in the JOIN clause with the corresponding destination vertices.</li>
+     * </ul>
      */
     public static class EdgeConstructor extends AbstractLangExpression {
         private final List<Integer> destinationKeySourceIndicators;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/PathPatternExpr.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/PathPatternExpr.java
index 1057731..2e1e7c2 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/PathPatternExpr.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/PathPatternExpr.java
@@ -21,18 +21,23 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractExpression;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 /**
- * A path is composed of a list of {@link VertexPatternExpr} instances and a list of {@link EdgePatternExpr} that
- * utilize the aforementioned vertices. Users can also optionally specify a variable, and attach {@link LetClause} nodes
- * to aid in lowering this expression (i.e. for lowering sub-paths).
+ * A path is composed of:
+ * <ul>
+ *  <li>A list of {@link VertexPatternExpr} instances.</li>
+ *  <li>A list of {@link EdgePatternExpr} instances that utilize the aforementioned vertices.</li>
+ *  <li>An optional variable binding all vertices and edges to a path record.</li>
+ *  <li>A list of {@link LetClause} nodes that represent expanded sub-paths.</li>
+ * </ul>
  */
 public class PathPatternExpr extends AbstractExpression {
     private final List<LetClause> reboundSubPathExpressions;
@@ -79,4 +84,13 @@
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
         return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
     }
+
+    @Override
+    public String toString() {
+        String edgeString = edgeExpressions.stream().map(EdgePatternExpr::toString).collect(Collectors.joining(","));
+        String variableString = (variableExpr != null) ? (" AS " + variableExpr) : "";
+        return String.format("%s%s%s",
+                vertexExpressions.stream().map(VertexPatternExpr::toString).collect(Collectors.joining(",")),
+                (edgeString.equals("") ? "" : ", " + edgeString), variableString);
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/VertexPatternExpr.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/VertexPatternExpr.java
index 54cbfa4..0897eb6 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/VertexPatternExpr.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/VertexPatternExpr.java
@@ -24,24 +24,31 @@
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 /**
- * A query vertex (not to be confused with a vertex constructor) is composed of a set of labels (which may be empty)
- * and a variable (which may initially be null).
+ * A query vertex (not to be confused with a vertex constructor) is composed of:
+ * <ul>
+ *  <li>A set of labels (which may be empty).</li>
+ *  <li>A variable (which may initially be null).</li>
+ *  <li>A filter expression (which may be null).</li>
+ * </ul>
  */
 public class VertexPatternExpr extends AbstractExpression {
     private final Set<ElementLabel> labels;
+    private final Expression filterExpr;
     private VariableExpr variableExpr;
 
-    public VertexPatternExpr(VariableExpr variableExpr, Set<ElementLabel> labels) {
+    public VertexPatternExpr(VariableExpr variableExpr, Expression filterExpr, Set<ElementLabel> labels) {
         this.variableExpr = variableExpr;
+        this.filterExpr = filterExpr;
         this.labels = labels;
     }
 
@@ -49,6 +56,10 @@
         return labels;
     }
 
+    public Expression getFilterExpr() {
+        return filterExpr;
+    }
+
     public VariableExpr getVariableExpr() {
         return variableExpr;
     }
@@ -57,10 +68,8 @@
         this.variableExpr = variableExpr;
     }
 
-    public List<GraphElementIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
-        return labels.stream()
-                .map(v -> new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.VERTEX, v))
-                .collect(Collectors.toList());
+    public List<VertexIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
+        return labels.stream().map(v -> new VertexIdentifier(graphIdentifier, v)).collect(Collectors.toList());
     }
 
     @Override
@@ -77,14 +86,16 @@
             return false;
         }
         VertexPatternExpr that = (VertexPatternExpr) object;
-        return Objects.equals(this.labels, that.labels) && Objects.equals(this.variableExpr, that.variableExpr);
+        return Objects.equals(this.labels, that.labels) && Objects.equals(this.variableExpr, that.variableExpr)
+                && Objects.equals(this.filterExpr, that.filterExpr);
     }
 
     @Override
     public String toString() {
         String labelsString = labels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
         String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
-        return String.format("(%s:%s)", variableString, labelsString);
+        String filterString = (filterExpr != null) ? (" WHERE " + filterExpr + " ") : "";
+        return String.format("(%s:%s%s)", variableString, labelsString, filterString);
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphixParserHint.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphixParserHint.java
new file mode 100644
index 0000000..95e7403
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphixParserHint.java
@@ -0,0 +1,45 @@
+/*
+ * 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.asterix.graphix.lang.parser;
+
+import org.apache.asterix.graphix.algebra.compiler.option.ElementEvaluationOption;
+
+/**
+ * Graphix SQL++ specific hints. Note that this is not an extension of the SQL++ hint class:
+ * {@link org.apache.asterix.lang.sqlpp.parser.SqlppHint}, so callers must use their own facilities for hint finding.
+ */
+public enum GraphixParserHint {
+    EXPAND_AND_UNION_HINT(ElementEvaluationOption.EXPAND_AND_UNION.getOptionValue()),
+    SWITCH_AND_CYCLE_HINT(ElementEvaluationOption.SWITCH_AND_CYCLE.getOptionValue());
+
+    private final String id;
+
+    GraphixParserHint(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public String toString() {
+        return getId();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixQueryRewriter.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixQueryRewriter.java
new file mode 100644
index 0000000..7355a4d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixQueryRewriter.java
@@ -0,0 +1,343 @@
+/*
+ * 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.asterix.graphix.lang.rewrite;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.common.config.CompilerProperties;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
+import org.apache.asterix.graphix.lang.rewrite.common.BranchLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.print.SqlppASTPrintQueryVisitor;
+import org.apache.asterix.graphix.lang.rewrite.resolve.ExhaustiveSearchResolver;
+import org.apache.asterix.graphix.lang.rewrite.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.rewrite.visitor.ElementLookupTableVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.FunctionResolutionVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixFunctionCallVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixLoweringVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.PatternGraphGroupVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.PopulateUnknownsVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.PostRewriteCheckVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.PreRewriteCheckVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.QueryCanonicalizationVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.SubqueryVertexJoinVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.VariableScopingCheckVisitor;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.struct.PatternGroup;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.base.IParserFactory;
+import org.apache.asterix.lang.common.base.IReturningStatement;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.statement.Query;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.common.util.ExpressionUtils;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.lang.sqlpp.rewrites.SqlppFunctionBodyRewriter;
+import org.apache.asterix.lang.sqlpp.rewrites.SqlppQueryRewriter;
+import org.apache.asterix.metadata.entities.Dataverse;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.util.LogRedactionUtil;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Rewriter for Graphix queries, which will lower all graph AST nodes into SQL++ AST nodes. We perform the following:
+ * <ol>
+ *  <li>Perform an error-checking on the fresh AST (immediately after parsing).</li>
+ *  <li>Populate the unknowns in our AST (e.g. vertex / edge variables, projections, GROUP-BY keys).</li>
+ *  <li>Perform a variable-scoping pass to identify illegal variables (either duplicate or out-of-scope).</li>
+ *  <li>Resolve all of our function calls (Graphix, SQL++, and user-defined).</li>
+ *  <li>Perform resolution of unlabeled vertices / edges, as well as edge directions.</li>
+ *  <li>For all Graphix subqueries whose vertices are correlated, rewrite this correlation to be explicit.</li>
+ *  <li>Using the labels of the vertices / edges in our AST, fetch the relevant graph elements from our metadata.</li>
+ *  <li>Perform a canonical Graphix lowering pass to remove ambiguities (e.g. undirected edges).</li>
+ *  <li>Perform a lowering pass to transform Graphix AST nodes to SQL++ AST nodes.</li>
+ *  <li>Perform another lowering pass to transform Graphix CALL-EXPR nodes to SQL++ AST nodes.</li>
+ *  <li>Perform all SQL++ rewrites on our newly lowered AST.</li>
+ * </ol>
+ */
+public class GraphixQueryRewriter extends SqlppQueryRewriter {
+    private static final Logger LOGGER = LogManager.getLogger(GraphixQueryRewriter.class);
+
+    private final GraphixParserFactory parserFactory;
+    private final SqlppQueryRewriter bodyRewriter;
+
+    public GraphixQueryRewriter(IParserFactory parserFactory) {
+        super(parserFactory);
+        this.parserFactory = (GraphixParserFactory) parserFactory;
+        this.bodyRewriter = getFunctionAndViewBodyRewriter();
+    }
+
+    @Override
+    public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
+            boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+            throws CompilationException {
+        LOGGER.debug("Starting Graphix AST rewrites.");
+
+        // Perform an initial error-checking pass to validate our user query.
+        LOGGER.trace("Performing pre-Graphix-rewrite check (user query validation).");
+        GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
+        topStatement.accept(new PreRewriteCheckVisitor(graphixRewritingContext), null);
+
+        // Perform the Graphix rewrites.
+        rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
+
+        // Sanity check: ensure that no graph AST nodes exist after this point.
+        Map<String, Object> queryConfig = graphixRewritingContext.getMetadataProvider().getConfig();
+        if (queryConfig.containsKey(CompilerProperties.COMPILER_INTERNAL_SANITYCHECK_KEY)) {
+            String configValue = (String) queryConfig.get(CompilerProperties.COMPILER_INTERNAL_SANITYCHECK_KEY);
+            if (!configValue.equalsIgnoreCase("false")) {
+                LOGGER.trace("Performing post-Graphix-rewrite check (making sure no graph AST nodes exist).");
+                topStatement.accept(new PostRewriteCheckVisitor(), null);
+            }
+        }
+
+        // Perform the remainder of the SQL++ rewrites.
+        LOGGER.debug("Ending Graphix AST rewrites. Now starting SQL++ AST rewrites.");
+        rewriteSQLPPASTNodes(langRewritingContext, topStatement, allowNonStoredUDFCalls, inlineUdfsAndViews,
+                externalVars);
+
+        // Update the variable counter on our context.
+        topStatement.setVarCounter(graphixRewritingContext.getVarCounter().get());
+        LOGGER.debug("Ending SQL++ AST rewrites.");
+    }
+
+    public void loadNormalizedGraphElement(GraphixRewritingContext graphixRewritingContext,
+            GraphElementDeclaration graphElementDeclaration) throws CompilationException {
+        if (graphElementDeclaration.getNormalizedBody() == null) {
+            Dataverse defaultDataverse = graphixRewritingContext.getMetadataProvider().getDefaultDataverse();
+            Dataverse targetDataverse;
+
+            // We might need to change our dataverse, if the element definition requires a different one.
+            GraphIdentifier graphIdentifier = graphElementDeclaration.getGraphIdentifier();
+            DataverseName graphDataverseName = graphIdentifier.getDataverseName();
+            if (graphDataverseName == null || graphDataverseName.equals(defaultDataverse.getDataverseName())) {
+                targetDataverse = defaultDataverse;
+
+            } else {
+                try {
+                    targetDataverse = graphixRewritingContext.getMetadataProvider().findDataverse(graphDataverseName);
+
+                } catch (AlgebricksException e) {
+                    throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e,
+                            graphElementDeclaration.getSourceLocation(), graphDataverseName);
+                }
+            }
+            graphixRewritingContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
+
+            // Get the body of the rewritten query.
+            Expression rawBody = graphElementDeclaration.getRawBody();
+            try {
+                SourceLocation sourceLocation = graphElementDeclaration.getSourceLocation();
+                Query wrappedQuery = ExpressionUtils.createWrappedQuery(rawBody, sourceLocation);
+                bodyRewriter.rewrite(graphixRewritingContext, wrappedQuery, false, false, List.of());
+                graphElementDeclaration.setNormalizedBody(wrappedQuery.getBody());
+
+            } catch (CompilationException e) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, rawBody.getSourceLocation(),
+                        "Bad definition for a graph element: " + e.getMessage());
+
+            } finally {
+                // Switch back to the working dataverse.
+                graphixRewritingContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
+            }
+        }
+    }
+
+    /**
+     * Lower a Graphix AST into a SQLPP AST. The only nodes that should survive are the following:
+     * 1. {@link org.apache.asterix.graphix.lang.clause.FromGraphClause}
+     * 2. {@link LowerListClause}
+     * 3. {@link LowerSwitchClause}
+     */
+    public void rewriteGraphixASTNodes(GraphixRewritingContext graphixRewritingContext,
+            IReturningStatement topStatement, boolean allowNonStoredUDFCalls) throws CompilationException {
+        // Generate names for unnamed graph elements, projections in our SELECT CLAUSE, and keys in our GROUP BY.
+        LOGGER.trace("Populating unknowns (both graph and non-graph) in our AST.");
+        rewriteExpr(topStatement, new PopulateUnknownsVisitor(graphixRewritingContext));
+
+        // Verify that variables are properly within scope.
+        LOGGER.trace("Verifying that variables are unique and are properly scoped.");
+        rewriteExpr(topStatement, new VariableScopingCheckVisitor(graphixRewritingContext));
+
+        // Resolve all of our (Graphix, SQL++, and user-defined) function calls.
+        LOGGER.trace("Resolving Graphix, SQL++, and user-defined function calls.");
+        rewriteExpr(topStatement, new FunctionResolutionVisitor(graphixRewritingContext, allowNonStoredUDFCalls));
+
+        // Rewrite implicit correlated vertex JOINs as explicit JOINs.
+        LOGGER.trace("Rewriting correlated implicit vertex JOINs into explicit JOINs.");
+        rewriteExpr(topStatement, new SubqueryVertexJoinVisitor(graphixRewritingContext));
+
+        // Resolve our vertex labels, edge labels, and edge directions.
+        LOGGER.trace("Performing label and edge direction resolution.");
+        Map<GraphIdentifier, SchemaKnowledgeTable> knowledgeTableMap = new HashMap<>();
+        Map<GraphIdentifier, PatternGroup> resolutionPatternMap = new HashMap<>();
+        topStatement.accept(new PatternGraphGroupVisitor(resolutionPatternMap, graphixRewritingContext) {
+            @Override
+            public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+                GraphIdentifier graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
+                SchemaKnowledgeTable schemaTable = new SchemaKnowledgeTable(fromGraphClause, graphixRewritingContext);
+                knowledgeTableMap.put(graphIdentifier, schemaTable);
+                return super.visit(fromGraphClause, arg);
+            }
+        }, null);
+        for (Map.Entry<GraphIdentifier, PatternGroup> mapEntry : resolutionPatternMap.entrySet()) {
+            SchemaKnowledgeTable knowledgeTable = knowledgeTableMap.get(mapEntry.getKey());
+            new ExhaustiveSearchResolver(knowledgeTable).resolve(mapEntry.getValue());
+        }
+
+        // Fetch all relevant graph element declarations, using the element labels.
+        LOGGER.trace("Fetching relevant edge and vertex bodies from our graph schema.");
+        ElementLookupTable elementLookupTable = new ElementLookupTable();
+        ElementLookupTableVisitor elementLookupTableVisitor =
+                new ElementLookupTableVisitor(graphixRewritingContext, elementLookupTable, parserFactory);
+        rewriteExpr(topStatement, elementLookupTableVisitor);
+        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
+            loadNormalizedGraphElement(graphixRewritingContext, graphElementDeclaration);
+        }
+
+        // Expand / enumerate vertex and edge patterns to snuff out all ambiguities.
+        LOGGER.trace("Performing a canonicalization pass to expand or enumerate patterns.");
+        BranchLookupTable branchLookupTable = new BranchLookupTable();
+        QueryCanonicalizationVisitor queryCanonicalizationVisitor =
+                new QueryCanonicalizationVisitor(branchLookupTable, graphixRewritingContext);
+        rewriteExpr(topStatement, queryCanonicalizationVisitor);
+
+        // Transform all graph AST nodes (i.e. perform the representation lowering).
+        LOGGER.trace("Lowering the Graphix AST-specific nodes representation to a SQL++ representation.");
+        GraphixLoweringVisitor graphixLoweringVisitor =
+                new GraphixLoweringVisitor(graphixRewritingContext, elementLookupTable, branchLookupTable);
+        rewriteExpr(topStatement, graphixLoweringVisitor);
+
+        // Lower all of our Graphix function calls (and perform schema-enrichment).
+        LOGGER.trace("Lowering the Graphix CALL-EXPR nodes to a pure SQL++ representation.");
+        rewriteExpr(topStatement, new GraphixFunctionCallVisitor(graphixRewritingContext));
+    }
+
+    /**
+     * Rewrite a SQLPP AST. We do not perform the following:
+     * <ul>
+     *  <li>Function call resolution (this is handled in {@link FunctionResolutionVisitor}).</li>
+     *  <li>Column name generation (this is handled in {@link PopulateUnknownsVisitor}).</li>
+     *  <li>SQL-compat rewrites (not supported).</li>
+     * </ul>
+     */
+    public void rewriteSQLPPASTNodes(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
+            boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+            throws CompilationException {
+        super.setup(langRewritingContext, topStatement, externalVars, allowNonStoredUDFCalls, inlineUdfsAndViews);
+        super.substituteGroupbyKeyExpression();
+        super.rewriteGroupBys();
+        super.rewriteSetOperations();
+        super.inlineColumnAlias();
+        super.rewriteWindowExpressions();
+        super.rewriteGroupingSets();
+        super.variableCheckAndRewrite();
+        super.extractAggregatesFromCaseExpressions();
+        super.rewriteGroupByAggregationSugar();
+        super.rewriteWindowAggregationSugar();
+        super.rewriteOperatorExpression();
+        super.rewriteCaseExpressions();
+        super.rewriteListInputFunctions();
+        super.rewriteRightJoins();
+        super.loadAndInlineUdfsAndViews();
+        super.rewriteSpecialFunctionNames();
+        super.inlineWithExpressions();
+    }
+
+    @Override
+    protected SqlppFunctionBodyRewriter getFunctionAndViewBodyRewriter() {
+        return new SqlppFunctionBodyRewriter(parserFactory) {
+            @Override
+            public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
+                    boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+                    throws CompilationException {
+                // Perform an initial error-checking pass to validate our body.
+                GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
+                topStatement.accept(new PreRewriteCheckVisitor(graphixRewritingContext), null);
+
+                // Perform the Graphix rewrites.
+                rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
+
+                // Sanity check: ensure that no graph AST nodes exist after this point.
+                Map<String, Object> queryConfig = graphixRewritingContext.getMetadataProvider().getConfig();
+                if (queryConfig.containsKey(CompilerProperties.COMPILER_INTERNAL_SANITYCHECK_KEY)) {
+                    String configValue = (String) queryConfig.get(CompilerProperties.COMPILER_INTERNAL_SANITYCHECK_KEY);
+                    if (!configValue.equalsIgnoreCase("false")) {
+                        topStatement.accept(new PostRewriteCheckVisitor(), null);
+                    }
+                }
+
+                // Perform the remainder of the SQL++ (body specific) rewrites.
+                super.setup(langRewritingContext, topStatement, externalVars, allowNonStoredUDFCalls,
+                        inlineUdfsAndViews);
+                super.substituteGroupbyKeyExpression();
+                super.rewriteGroupBys();
+                super.rewriteSetOperations();
+                super.inlineColumnAlias();
+                super.rewriteWindowExpressions();
+                super.rewriteGroupingSets();
+                super.variableCheckAndRewrite();
+                super.extractAggregatesFromCaseExpressions();
+                super.rewriteGroupByAggregationSugar();
+                super.rewriteWindowAggregationSugar();
+                super.rewriteOperatorExpression();
+                super.rewriteCaseExpressions();
+                super.rewriteListInputFunctions();
+                super.rewriteRightJoins();
+
+                // Update the variable counter in our context.
+                topStatement.setVarCounter(langRewritingContext.getVarCounter().get());
+            }
+        };
+    }
+
+    private <R, T> void rewriteExpr(IReturningStatement returningStatement, ILangVisitor<R, T> visitor)
+            throws CompilationException {
+        if (LOGGER.isTraceEnabled()) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter printWriter = new PrintWriter(stringWriter);
+            returningStatement.accept(new SqlppASTPrintQueryVisitor(printWriter), null);
+            String planAsString = LogRedactionUtil.userData(stringWriter.toString());
+            LOGGER.trace("Plan before rewrite: {}\n", planAsString);
+        }
+        returningStatement.accept(visitor, null);
+        if (LOGGER.isTraceEnabled()) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter printWriter = new PrintWriter(stringWriter);
+            returningStatement.accept(new SqlppASTPrintQueryVisitor(printWriter), null);
+            String planAsString = LogRedactionUtil.userData(stringWriter.toString());
+            LOGGER.trace("Plan after rewrite: {}\n", planAsString);
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewriterFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewriterFactory.java
similarity index 96%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewriterFactory.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewriterFactory.java
index 6b1cf30..7b6b79b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewriterFactory.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewriterFactory.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites;
+package org.apache.asterix.graphix.lang.rewrite;
 
 import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
 import org.apache.asterix.lang.common.base.IParserFactory;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewritingContext.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewritingContext.java
new file mode 100644
index 0000000..6018fce
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/GraphixRewritingContext.java
@@ -0,0 +1,129 @@
+/*
+ * 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.asterix.graphix.lang.rewrite;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.algebra.compiler.option.ElementEvaluationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.IGraphixCompilerOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateEdgeOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateVertexOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsNavigationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsPatternOption;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.statement.ViewDecl;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
+
+/**
+ * Wrapper class for {@link LangRewritingContext} and for Graphix specific rewriting.
+ */
+public class GraphixRewritingContext extends LangRewritingContext {
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs = new HashMap<>();
+    private final Map<String, Integer> uniqueCopyCounter = new HashMap<>();
+    private final Map<String, String> configFileOptions;
+
+    public GraphixRewritingContext(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions,
+            List<ViewDecl> declaredViews, Set<DeclareGraphStatement> declareGraphStatements,
+            IWarningCollector warningCollector, int varCounter, Map<String, String> configFileOptions) {
+        super(metadataProvider, declaredFunctions, declaredViews, warningCollector, varCounter);
+        declareGraphStatements.forEach(d -> {
+            GraphIdentifier graphIdentifier = new GraphIdentifier(d.getDataverseName(), d.getGraphName());
+            this.declaredGraphs.put(graphIdentifier, d);
+        });
+        this.configFileOptions = configFileOptions;
+    }
+
+    public Map<GraphIdentifier, DeclareGraphStatement> getDeclaredGraphs() {
+        return declaredGraphs;
+    }
+
+    public VariableExpr getGraphixVariableCopy(String existingIdentifierValue) {
+        uniqueCopyCounter.put(existingIdentifierValue, uniqueCopyCounter.getOrDefault(existingIdentifierValue, 0) + 1);
+        int currentCount = uniqueCopyCounter.get(existingIdentifierValue);
+        String variableName = String.format("#GGVC(%s,%s)", existingIdentifierValue, currentCount);
+        return new VariableExpr(new VarIdentifier(variableName));
+    }
+
+    public VariableExpr getGraphixVariableCopy(VariableExpr existingVariable) {
+        VarIdentifier existingIdentifier = existingVariable.getVar();
+        String variableName = SqlppVariableUtil.toUserDefinedVariableName(existingIdentifier).getValue();
+        return getGraphixVariableCopy(variableName);
+    }
+
+    public IGraphixCompilerOption getSetting(String settingName) throws CompilationException {
+        IGraphixCompilerOption[] enumValues;
+        switch (settingName) {
+            case ElementEvaluationOption.OPTION_KEY_NAME:
+                enumValues = ElementEvaluationOption.values();
+                return parseSetting(settingName, enumValues, ElementEvaluationOption.OPTION_DEFAULT);
+
+            case SchemaDecorateEdgeOption.OPTION_KEY_NAME:
+                enumValues = SchemaDecorateEdgeOption.values();
+                return parseSetting(settingName, enumValues, SchemaDecorateEdgeOption.OPTION_DEFAULT);
+
+            case SchemaDecorateVertexOption.OPTION_KEY_NAME:
+                enumValues = SchemaDecorateVertexOption.values();
+                return parseSetting(settingName, enumValues, SchemaDecorateVertexOption.OPTION_DEFAULT);
+
+            case SemanticsNavigationOption.OPTION_KEY_NAME:
+                enumValues = SemanticsNavigationOption.values();
+                return parseSetting(settingName, enumValues, SemanticsNavigationOption.OPTION_DEFAULT);
+
+            case SemanticsPatternOption.OPTION_KEY_NAME:
+                enumValues = SemanticsPatternOption.values();
+                return parseSetting(settingName, enumValues, SemanticsPatternOption.OPTION_DEFAULT);
+
+            default:
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Illegal setting requested!");
+        }
+    }
+
+    private IGraphixCompilerOption parseSetting(String settingName, IGraphixCompilerOption[] settingValues,
+            IGraphixCompilerOption defaultValue) throws CompilationException {
+        // Always check our metadata configuration first.
+        Object metadataConfigValue = getMetadataProvider().getConfig().get(settingName);
+        if (metadataConfigValue != null) {
+            String configValueString = ((String) metadataConfigValue).toLowerCase(Locale.ROOT);
+            return Stream.of(settingValues).filter(o -> o.getOptionValue().equals(configValueString)).findFirst()
+                    .orElseThrow(() -> new CompilationException(ErrorCode.PARAMETER_NO_VALUE, configValueString));
+        }
+
+        // If our setting is not in metadata, check our config file.
+        String configFileValue = configFileOptions.get(settingName);
+        if (configFileValue != null) {
+            return Stream.of(settingValues).filter(o -> o.getOptionValue().equals(configFileValue)).findFirst()
+                    .orElseThrow(() -> new CompilationException(ErrorCode.PARAMETER_NO_VALUE, configFileValue));
+        }
+
+        // Otherwise, return our default value.
+        return defaultValue;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementBranchConsumer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementBranchConsumer.java
new file mode 100644
index 0000000..8bd208b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementBranchConsumer.java
@@ -0,0 +1,61 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.canonical;
+
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.common.BranchLookupTable;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+
+public class CanonicalElementBranchConsumer implements ICanonicalElementConsumer {
+    private final BranchLookupTable branchLookupTable;
+
+    public CanonicalElementBranchConsumer(BranchLookupTable branchLookupTable) {
+        this.branchLookupTable = branchLookupTable;
+    }
+
+    @Override
+    public void accept(AbstractExpression ambiguousElement, List<? extends AbstractExpression> canonicalElements)
+            throws CompilationException {
+        if (ambiguousElement instanceof VertexPatternExpr) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                    "Cannot evaluate an ambiguous dangling vertex using SWITCH_AND_CYCLE. Try EXPAND_AND_UNION.",
+                    ambiguousElement.getSourceLocation());
+        }
+        EdgePatternExpr ambiguousEdgeElement = (EdgePatternExpr) ambiguousElement;
+        if (ambiguousEdgeElement.getEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.EDGE) {
+            for (AbstractExpression canonicalElement : canonicalElements) {
+                EdgePatternExpr canonicalEdge = (EdgePatternExpr) canonicalElement;
+                branchLookupTable.putBranch(ambiguousEdgeElement, canonicalEdge);
+            }
+
+        } else { // ambiguousEdgeElement.getEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.PATH
+            for (AbstractExpression canonicalElement : canonicalElements) {
+                PathPatternExpr canonicalPath = (PathPatternExpr) canonicalElement;
+                branchLookupTable.putBranch(ambiguousEdgeElement, canonicalPath);
+            }
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementExpansionConsumer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementExpansionConsumer.java
new file mode 100644
index 0000000..a35e61a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementExpansionConsumer.java
@@ -0,0 +1,331 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.canonical;
+
+import static org.apache.asterix.graphix.lang.rewrite.lower.action.PathPatternAction.buildPathRecord;
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.replaceEdgeInIterator;
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.replaceVertexInIterator;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.annotation.SubqueryVertexJoinAnnotation;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+
+public class CanonicalElementExpansionConsumer implements ICanonicalElementConsumer {
+    private final GraphixDeepCopyVisitor deepCopyVisitor = new GraphixDeepCopyVisitor();
+    private final Deque<SelectBlock> blackSelectBlockStack = new ArrayDeque<>();
+    private final Deque<SelectBlock> redSelectBlockStack = new ArrayDeque<>();
+    private final GraphixRewritingContext graphixRewritingContext;
+
+    // We should return a copy of our original SELECT-EXPR.
+    private final Set<SetOperationInput> remainingSetOpInputs;
+
+    public CanonicalElementExpansionConsumer(SelectExpression originalSelectExpression,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.remainingSetOpInputs = new HashSet<>();
+        this.remainingSetOpInputs.add(originalSelectExpression.getSelectSetOperation().getLeftInput());
+        this.remainingSetOpInputs.addAll(originalSelectExpression.getSelectSetOperation().getRightInputs().stream()
+                .map(SetOperationRight::getSetOperationRightInput).collect(Collectors.toSet()));
+    }
+
+    @Override
+    public void accept(AbstractExpression ambiguousElement, List<? extends AbstractExpression> canonicalElements)
+            throws CompilationException {
+        Deque<SelectBlock> readStack, writeStack;
+        if (blackSelectBlockStack.isEmpty()) {
+            writeStack = blackSelectBlockStack;
+            readStack = redSelectBlockStack;
+
+        } else {
+            writeStack = redSelectBlockStack;
+            readStack = blackSelectBlockStack;
+        }
+
+        ICanonicalPatternUpdater pathPatternReplacer;
+        if (ambiguousElement instanceof VertexPatternExpr) {
+            pathPatternReplacer = new VertexPatternUpdater(ambiguousElement);
+
+        } else { // ambiguousElement instanceof EdgePatternExpr
+            EdgePatternExpr ambiguousEdgePattern = (EdgePatternExpr) ambiguousElement;
+            EdgeDescriptor ambiguousEdgeDescriptor = ambiguousEdgePattern.getEdgeDescriptor();
+            if (ambiguousEdgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
+                pathPatternReplacer = new EdgePatternUpdater(ambiguousElement);
+
+            } else { // ambiguousEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.PATH
+                pathPatternReplacer = new PathPatternUpdater(ambiguousElement);
+            }
+        }
+
+        // Consume all of our read stack.
+        while (!readStack.isEmpty()) {
+            SelectBlock workingSelectBlock = readStack.pop();
+            for (AbstractExpression canonicalElement : canonicalElements) {
+                SelectBlock workingSelectBlockCopy = deepCopyVisitor.visit(workingSelectBlock, null);
+                workingSelectBlockCopy.accept(new AbstractGraphixQueryVisitor() {
+                    @Override
+                    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg)
+                            throws CompilationException {
+                        pathPatternReplacer.accept(canonicalElement, pathPatternExpr);
+                        return pathPatternExpr;
+                    }
+                }, null);
+                writeStack.push(workingSelectBlockCopy);
+            }
+        }
+    }
+
+    public void finalize(SelectExpression selectExpression, Consumer<SelectBlock> selectBlockCallback) {
+        SelectSetOperation selectSetOperation = selectExpression.getSelectSetOperation();
+        SetOperationInput leftSetOperationInput = selectSetOperation.getLeftInput();
+
+        // Exhaust all of our generated SELECT-BLOCKs.
+        Deque<SelectBlock> finalStack = (redSelectBlockStack.isEmpty()) ? blackSelectBlockStack : redSelectBlockStack;
+        if (!remainingSetOpInputs.contains(leftSetOperationInput)) {
+            leftSetOperationInput.setSelectBlock(finalStack.peek());
+            selectBlockCallback.accept(finalStack.pop());
+        }
+        for (SetOperationRight rightInput : selectSetOperation.getRightInputs()) {
+            SetOperationInput rightSetOperationInput = rightInput.getSetOperationRightInput();
+            if (!remainingSetOpInputs.contains(rightSetOperationInput)) {
+                rightSetOperationInput.setSelectBlock(finalStack.peek());
+                selectBlockCallback.accept(finalStack.pop());
+            }
+        }
+        while (!finalStack.isEmpty()) {
+            SetOperationInput newSetOpInput = new SetOperationInput(finalStack.peek(), null);
+            selectSetOperation.getRightInputs().add(new SetOperationRight(SetOpType.UNION, false, newSetOpInput));
+            selectBlockCallback.accept(finalStack.pop());
+        }
+    }
+
+    public void resetSelectBlock(SelectBlock selectBlock) {
+        blackSelectBlockStack.clear();
+        redSelectBlockStack.clear();
+        blackSelectBlockStack.push(selectBlock);
+
+        // Remove from our original SET-OP input set, the SET-OP this SELECT-BLOCK corresponds to.
+        remainingSetOpInputs.removeIf(s -> s.selectBlock() && s.getSelectBlock().equals(selectBlock));
+    }
+
+    // We provide the following interface to handle dangling vertices, edges, and paths.
+    @FunctionalInterface
+    private interface ICanonicalPatternUpdater {
+        void accept(AbstractExpression canonicalExpr, PathPatternExpr pathPatternExpr) throws CompilationException;
+    }
+
+    private class VertexPatternUpdater implements ICanonicalPatternUpdater {
+        private final AbstractExpression ambiguousElement;
+
+        private VertexPatternUpdater(AbstractExpression ambiguousElement) {
+            this.ambiguousElement = ambiguousElement;
+        }
+
+        @Override
+        public void accept(AbstractExpression canonicalExpr, PathPatternExpr pathPatternExpr)
+                throws CompilationException {
+            List<VertexPatternExpr> vertexExpressions = pathPatternExpr.getVertexExpressions();
+            ListIterator<VertexPatternExpr> vertexIterator = vertexExpressions.listIterator();
+            VertexPatternExpr ambiguousVertex = (VertexPatternExpr) ambiguousElement;
+            VertexPatternExpr canonicalVertex = (VertexPatternExpr) canonicalExpr;
+
+            // Our replacement map must also include any subquery-correlated-join vertices.
+            Map<VertexPatternExpr, VertexPatternExpr> replacementMap = new HashMap<>();
+            replacementMap.put(ambiguousVertex, canonicalVertex);
+            for (VertexPatternExpr vertexPatternExpr : vertexExpressions) {
+                if (vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class) != null) {
+                    SubqueryVertexJoinAnnotation hint = vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                    if (hint.getSourceVertexVariable().equals(ambiguousVertex.getVariableExpr())) {
+                        VertexPatternExpr canonicalVertexCopy = deepCopyVisitor.visit(canonicalVertex, null);
+                        canonicalVertexCopy.setVariableExpr(vertexPatternExpr.getVariableExpr());
+                        replacementMap.put(vertexPatternExpr, canonicalVertexCopy);
+                    }
+                }
+            }
+            replaceVertexInIterator(replacementMap, vertexIterator, deepCopyVisitor);
+        }
+    }
+
+    private class EdgePatternUpdater implements ICanonicalPatternUpdater {
+        private final AbstractExpression ambiguousElement;
+
+        private EdgePatternUpdater(AbstractExpression ambiguousElement) {
+            this.ambiguousElement = ambiguousElement;
+        }
+
+        @Override
+        public void accept(AbstractExpression canonicalExpr, PathPatternExpr pathPatternExpr)
+                throws CompilationException {
+            EdgePatternExpr ambiguousEdgePattern = (EdgePatternExpr) ambiguousElement;
+            VertexPatternExpr ambiguousLeftVertex = ambiguousEdgePattern.getLeftVertex();
+            VertexPatternExpr ambiguousRightVertex = ambiguousEdgePattern.getRightVertex();
+            EdgePatternExpr canonicalEdge = (EdgePatternExpr) canonicalExpr;
+            VertexPatternExpr canonicalLeftVertex = canonicalEdge.getLeftVertex();
+            VertexPatternExpr canonicalRightVertex = canonicalEdge.getRightVertex();
+
+            // Iterate through our edge list.
+            List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
+            ListIterator<EdgePatternExpr> edgeIterator = edgeExpressions.listIterator();
+            replaceEdgeInIterator(Map.of(ambiguousEdgePattern, canonicalEdge), edgeIterator, deepCopyVisitor);
+
+            // Iterate through our vertex list.
+            replaceEdgeVerticesAndJoinCopiesInIterator(pathPatternExpr, ambiguousLeftVertex, ambiguousRightVertex,
+                    canonicalLeftVertex, canonicalRightVertex);
+        }
+    }
+
+    private class PathPatternUpdater implements ICanonicalPatternUpdater {
+        private final AbstractExpression ambiguousElement;
+
+        private PathPatternUpdater(AbstractExpression ambiguousElement) {
+            this.ambiguousElement = ambiguousElement;
+        }
+
+        @Override
+        public void accept(AbstractExpression canonicalExpr, PathPatternExpr pathPatternExpr)
+                throws CompilationException {
+            EdgePatternExpr ambiguousEdgePattern = (EdgePatternExpr) ambiguousElement;
+            VertexPatternExpr ambiguousLeftVertex = ambiguousEdgePattern.getLeftVertex();
+            VertexPatternExpr ambiguousRightVertex = ambiguousEdgePattern.getRightVertex();
+            EdgeDescriptor ambiguousEdgeDescriptor = ambiguousEdgePattern.getEdgeDescriptor();
+            VariableExpr edgeVariable = ambiguousEdgeDescriptor.getVariableExpr();
+            PathPatternExpr canonicalPathPatternExpr = (PathPatternExpr) canonicalExpr;
+            List<EdgePatternExpr> canonicalEdges = canonicalPathPatternExpr.getEdgeExpressions();
+            VertexPatternExpr canonicalLeftVertex = canonicalEdges.get(0).getLeftVertex();
+            VertexPatternExpr canonicalRightVertex = canonicalEdges.get(canonicalEdges.size() - 1).getRightVertex();
+
+            // Iterate through our edge list.
+            List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
+            ListIterator<EdgePatternExpr> edgeIterator = edgeExpressions.listIterator();
+            while (edgeIterator.hasNext()) {
+                EdgePatternExpr workingEdge = edgeIterator.next();
+                if (workingEdge.equals(ambiguousEdgePattern)) {
+                    edgeIterator.remove();
+
+                    // We need to generate new variables for each edge in our new path.
+                    List<EdgePatternExpr> canonicalEdgeListCopy = new ArrayList<>();
+                    for (EdgePatternExpr canonicalEdge : canonicalEdges) {
+                        EdgePatternExpr canonicalEdgeCopy = deepCopyVisitor.visit(canonicalEdge, null);
+                        EdgeDescriptor canonicalEdgeCopyDescriptor = canonicalEdgeCopy.getEdgeDescriptor();
+                        VariableExpr edgeVariableCopy = graphixRewritingContext.getGraphixVariableCopy(edgeVariable);
+                        canonicalEdgeCopyDescriptor.setVariableExpr(edgeVariableCopy);
+                        canonicalEdgeListCopy.add(canonicalEdgeCopy);
+                        edgeIterator.add(canonicalEdgeCopy);
+                    }
+
+                    // Determine our new vertex list (we want to keep the order of our current vertex list).
+                    List<VertexPatternExpr> canonicalVertexListCopy = new ArrayList<>();
+                    ListIterator<VertexPatternExpr> pathPatternVertexIterator =
+                            pathPatternExpr.getVertexExpressions().listIterator();
+                    while (pathPatternVertexIterator.hasNext()) {
+                        VertexPatternExpr currentPathPatternVertex = pathPatternVertexIterator.next();
+                        VariableExpr currentVariable = currentPathPatternVertex.getVariableExpr();
+                        VariableExpr ambiguousLeftVar = ambiguousLeftVertex.getVariableExpr();
+                        VariableExpr ambiguousRightVar = ambiguousRightVertex.getVariableExpr();
+                        if (currentVariable.equals(ambiguousLeftVar)) {
+                            // Add canonical path vertices.
+                            List<VertexPatternExpr> canonicalVertices = canonicalPathPatternExpr.getVertexExpressions();
+                            for (VertexPatternExpr vertexExpr : canonicalVertices) {
+                                canonicalVertexListCopy.add(deepCopyVisitor.visit(vertexExpr, null));
+                                VariableExpr vertexVar = vertexExpr.getVariableExpr();
+                                if (!vertexVar.equals(ambiguousLeftVar) && !vertexVar.equals(ambiguousRightVar)) {
+                                    pathPatternVertexIterator.add(vertexExpr);
+                                }
+                            }
+                        }
+                    }
+
+                    // Build a new path record.
+                    RecordConstructor pathRecord = new RecordConstructor();
+                    pathRecord.setSourceLocation(workingEdge.getSourceLocation());
+                    buildPathRecord(canonicalVertexListCopy, canonicalEdgeListCopy, pathRecord);
+                    LetClause pathBinding = new LetClause(deepCopyVisitor.visit(edgeVariable, null), pathRecord);
+                    pathPatternExpr.getReboundSubPathList().add(pathBinding);
+
+                } else {
+                    if (workingEdge.getLeftVertex().equals(ambiguousLeftVertex)) {
+                        workingEdge.setLeftVertex(deepCopyVisitor.visit(canonicalLeftVertex, null));
+                    }
+                    if (workingEdge.getRightVertex().equals(ambiguousRightVertex)) {
+                        workingEdge.setRightVertex(deepCopyVisitor.visit(canonicalRightVertex, null));
+                    }
+                }
+            }
+
+            // Iterate through our vertex list.
+            replaceEdgeVerticesAndJoinCopiesInIterator(pathPatternExpr, ambiguousLeftVertex, ambiguousRightVertex,
+                    canonicalLeftVertex, canonicalRightVertex);
+        }
+    }
+
+    private void replaceEdgeVerticesAndJoinCopiesInIterator(PathPatternExpr pathPatternExpr,
+            VertexPatternExpr ambiguousLeftVertex, VertexPatternExpr ambiguousRightVertex,
+            VertexPatternExpr canonicalLeftVertex, VertexPatternExpr canonicalRightVertex) throws CompilationException {
+        List<VertexPatternExpr> vertexExpressions = pathPatternExpr.getVertexExpressions();
+        ListIterator<VertexPatternExpr> vertexIterator = vertexExpressions.listIterator();
+        Map<VertexPatternExpr, VertexPatternExpr> replacementVertexMap = new HashMap<>();
+        replacementVertexMap.put(ambiguousLeftVertex, canonicalLeftVertex);
+        replacementVertexMap.put(ambiguousRightVertex, canonicalRightVertex);
+        for (VertexPatternExpr vertexPatternExpr : vertexExpressions) {
+            if (vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class) != null) {
+                SubqueryVertexJoinAnnotation hint = vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                if (hint.getSourceVertexVariable().equals(ambiguousLeftVertex.getVariableExpr())) {
+                    VertexPatternExpr canonicalVertexCopy = deepCopyVisitor.visit(canonicalLeftVertex, null);
+                    canonicalVertexCopy.setVariableExpr(vertexPatternExpr.getVariableExpr());
+                    replacementVertexMap.put(vertexPatternExpr, canonicalVertexCopy);
+
+                } else if (hint.getSourceVertexVariable().equals(ambiguousRightVertex.getVariableExpr())) {
+                    VertexPatternExpr canonicalVertexCopy = deepCopyVisitor.visit(canonicalRightVertex, null);
+                    canonicalVertexCopy.setVariableExpr(vertexPatternExpr.getVariableExpr());
+                    replacementVertexMap.put(vertexPatternExpr, canonicalVertexCopy);
+                }
+            }
+        }
+        replaceVertexInIterator(replacementVertexMap, vertexIterator, deepCopyVisitor);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementGeneratorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementGeneratorFactory.java
new file mode 100644
index 0000000..5720660
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/CanonicalElementGeneratorFactory.java
@@ -0,0 +1,189 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.canonical;
+
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.deepCopyPathPattern;
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.expandEdgeDirection;
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.expandFixedPathPattern;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.resolve.IInternalVertexSupplier;
+import org.apache.asterix.graphix.lang.rewrite.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor.EdgeDirection;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+/**
+ * Generate a list of canonical {@link VertexPatternExpr}, {@link EdgePatternExpr}, or {@link PathPatternExpr}
+ * instances, given input {@link VertexPatternExpr} or {@link EdgePatternExpr}. We expect the input elements to have
+ * all unknowns resolved-- this pass is to generate canonical elements with the knowledge that each label / direction
+ * is possible according to our graph schema.
+ */
+public class CanonicalElementGeneratorFactory {
+    private final GraphixDeepCopyVisitor deepCopyVisitor;
+    private final SchemaKnowledgeTable schemaKnowledgeTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+
+    public CanonicalElementGeneratorFactory(GraphixRewritingContext graphixRewritingContext,
+            SchemaKnowledgeTable schemaKnowledgeTable) {
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.schemaKnowledgeTable = schemaKnowledgeTable;
+    }
+
+    public List<VertexPatternExpr> generateCanonicalVertices(VertexPatternExpr vertexPatternExpr)
+            throws CompilationException {
+        List<VertexPatternExpr> canonicalVertexList = new ArrayList<>();
+        for (ElementLabel elementLabel : vertexPatternExpr.getLabels()) {
+            VertexPatternExpr vertexCopy = deepCopyVisitor.visit(vertexPatternExpr, null);
+            vertexCopy.getLabels().clear();
+            vertexCopy.getLabels().add(elementLabel);
+            canonicalVertexList.add(vertexCopy);
+        }
+        return canonicalVertexList;
+    }
+
+    public List<EdgePatternExpr> generateCanonicalEdges(EdgePatternExpr edgePatternExpr) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+
+        // Each variable below represents a loop nesting.
+        Set<ElementLabel> edgeLabelList = edgeDescriptor.getEdgeLabels();
+        Set<ElementLabel> leftLabelList = leftVertex.getLabels();
+        Set<ElementLabel> rightLabelList = rightVertex.getLabels();
+        Set<EdgeDirection> directionList = expandEdgeDirection(edgeDescriptor);
+
+        List<EdgePatternExpr> canonicalEdgeList = new ArrayList<>();
+        for (ElementLabel edgeLabel : edgeLabelList) {
+            for (ElementLabel leftLabel : leftLabelList) {
+                for (ElementLabel rightLabel : rightLabelList) {
+                    for (EdgeDirection edgeDirection : directionList) {
+
+                        // Generate an edge according to our loop parameters.
+                        EdgePatternExpr edgeCopy = deepCopyVisitor.visit(edgePatternExpr, null);
+                        edgeCopy.getLeftVertex().getLabels().clear();
+                        edgeCopy.getRightVertex().getLabels().clear();
+                        edgeCopy.getEdgeDescriptor().getEdgeLabels().clear();
+                        edgeCopy.getLeftVertex().getLabels().add(leftLabel);
+                        edgeCopy.getRightVertex().getLabels().add(rightLabel);
+                        edgeCopy.getEdgeDescriptor().getEdgeLabels().add(edgeLabel);
+                        edgeCopy.getEdgeDescriptor().setEdgeDirection(edgeDirection);
+
+                        // If we have a valid edge, insert this into our list.
+                        if (schemaKnowledgeTable.isValidEdge(edgeCopy)) {
+                            canonicalEdgeList.add(edgeCopy);
+                        }
+                    }
+                }
+            }
+        }
+        return canonicalEdgeList;
+    }
+
+    public List<PathPatternExpr> generateCanonicalPaths(EdgePatternExpr edgePatternExpr,
+            boolean minimizeExpansionLength) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+        VertexPatternExpr internalVertex = edgePatternExpr.getInternalVertex();
+
+        // Each variable below represents a loop nesting.
+        Set<ElementLabel> edgeLabelList = edgeDescriptor.getEdgeLabels();
+        Set<ElementLabel> leftLabelList = leftVertex.getLabels();
+        Set<ElementLabel> rightLabelList = rightVertex.getLabels();
+        Set<EdgeDirection> directionList = expandEdgeDirection(edgeDescriptor);
+        Set<ElementLabel> internalLabelList = internalVertex.getLabels();
+
+        // Determine the length of **expanded** path. For {N,M} w/ E labels... MIN(M, (1 + 2(E-1))).
+        int minimumExpansionLength, expansionLength;
+        if (minimizeExpansionLength) {
+            int maximumExpansionLength = 1 + 2 * (edgeLabelList.size() - 1);
+            minimumExpansionLength = Objects.requireNonNullElse(edgeDescriptor.getMinimumHops(), 1);
+            expansionLength = Objects.requireNonNullElse(edgeDescriptor.getMaximumHops(), maximumExpansionLength);
+            if (minimumExpansionLength > expansionLength) {
+                minimumExpansionLength = expansionLength;
+            }
+
+        } else {
+            minimumExpansionLength = Objects.requireNonNullElse(edgeDescriptor.getMinimumHops(), 1);
+            expansionLength = edgeDescriptor.getMaximumHops();
+        }
+
+        // Generate all valid paths, from minimumExpansionLength to maximumExpansionLength.
+        List<List<EdgePatternExpr>> canonicalPathList = new ArrayList<>();
+        IInternalVertexSupplier internalVertexSupplier = () -> {
+            VertexPatternExpr internalVertexCopy = deepCopyVisitor.visit(internalVertex, null);
+            VariableExpr internalVariable = internalVertex.getVariableExpr();
+            VariableExpr internalVariableCopy = graphixRewritingContext.getGraphixVariableCopy(internalVariable);
+            internalVertexCopy.setVariableExpr(internalVariableCopy);
+            return internalVertexCopy;
+        };
+        for (int pathLength = minimumExpansionLength; pathLength <= expansionLength; pathLength++) {
+            List<List<EdgePatternExpr>> expandedPathPattern = expandFixedPathPattern(pathLength, edgePatternExpr,
+                    edgeLabelList, internalLabelList, directionList, deepCopyVisitor, internalVertexSupplier);
+            for (List<EdgePatternExpr> pathPattern : expandedPathPattern) {
+                for (ElementLabel leftLabel : leftLabelList) {
+                    for (ElementLabel rightLabel : rightLabelList) {
+                        List<EdgePatternExpr> pathCopy = deepCopyPathPattern(pathPattern, deepCopyVisitor);
+
+                        // Set the labels of our leftmost vertex...
+                        pathCopy.get(0).getLeftVertex().getLabels().clear();
+                        pathCopy.get(0).getLeftVertex().getLabels().add(leftLabel);
+
+                        // ...and our rightmost vertex.
+                        pathCopy.get(pathCopy.size() - 1).getRightVertex().getLabels().clear();
+                        pathCopy.get(pathCopy.size() - 1).getRightVertex().getLabels().add(rightLabel);
+
+                        // Add our path if all edges are valid.
+                        if (pathCopy.stream().allMatch(schemaKnowledgeTable::isValidEdge)) {
+                            canonicalPathList.add(pathCopy);
+                        }
+                    }
+                }
+            }
+        }
+
+        // Wrap our edge lists into path patterns.
+        List<PathPatternExpr> pathPatternList = new ArrayList<>();
+        for (List<EdgePatternExpr> edgeList : canonicalPathList) {
+            List<VertexPatternExpr> vertexList = new ArrayList<>();
+            edgeList.forEach(e -> {
+                vertexList.add(e.getLeftVertex());
+                vertexList.add(e.getRightVertex());
+            });
+            VariableExpr variableExpr = deepCopyVisitor.visit(edgeDescriptor.getVariableExpr(), null);
+            PathPatternExpr pathPatternExpr = new PathPatternExpr(vertexList, edgeList, variableExpr);
+            pathPatternExpr.setSourceLocation(edgePatternExpr.getSourceLocation());
+            pathPatternList.add(pathPatternExpr);
+        }
+        return pathPatternList;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/ICanonicalElementConsumer.java
similarity index 74%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/ICanonicalElementConsumer.java
index 65f42e5..2392423 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/canonical/ICanonicalElementConsumer.java
@@ -16,14 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.canonical;
+package org.apache.asterix.graphix.lang.rewrite.canonical;
 
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.lang.common.base.AbstractExpression;
 
 @FunctionalInterface
-public interface ICanonicalExpander<T> {
-    void apply(T patternExpr, List<GraphSelectBlock> inputSelectBlocks) throws CompilationException;
+public interface ICanonicalElementConsumer {
+    void accept(AbstractExpression ambiguousElement, List<? extends AbstractExpression> canonicalElements)
+            throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/BranchLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/BranchLookupTable.java
new file mode 100644
index 0000000..a21e04a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/BranchLookupTable.java
@@ -0,0 +1,49 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.common;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+
+/**
+ * Lookup table for branch (i.e. partial {@link EdgePatternExpr} instances that define FA transition functions)--
+ * indexed by {@link EdgePatternExpr} instances.
+ */
+public class BranchLookupTable {
+    private final Map<EdgePatternExpr, List<EdgePatternExpr>> branchEdgeMap = new HashMap<>();
+
+    public void putBranch(EdgePatternExpr associatedEdge, EdgePatternExpr branch) {
+        branchEdgeMap.putIfAbsent(associatedEdge, new ArrayList<>());
+        branchEdgeMap.get(associatedEdge).add(branch);
+    }
+
+    public void putBranch(EdgePatternExpr associatedEdge, PathPatternExpr branch) {
+        branchEdgeMap.putIfAbsent(associatedEdge, new ArrayList<>());
+        branchEdgeMap.get(associatedEdge).addAll(branch.getEdgeExpressions());
+    }
+
+    public List<EdgePatternExpr> getBranches(EdgePatternExpr associatedEdge) {
+        return branchEdgeMap.get(associatedEdge);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/ElementLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/ElementLookupTable.java
new file mode 100644
index 0000000..bf1eb62
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/ElementLookupTable.java
@@ -0,0 +1,75 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.common;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+
+/**
+ * Lookup table for {@link GraphElementDeclaration} instances, vertex keys, edge destination keys, and edge source
+ * keys-- indexed by {@link IElementIdentifier} instances.
+ */
+public class ElementLookupTable implements Iterable<GraphElementDeclaration> {
+    private final Map<IElementIdentifier, GraphElementDeclaration> graphElementDeclMap = new HashMap<>();
+    private final Map<VertexIdentifier, List<List<String>>> vertexKeyMap = new HashMap<>();
+    private final Map<EdgeIdentifier, List<List<String>>> edgeDestKeysMap = new HashMap<>();
+    private final Map<EdgeIdentifier, List<List<String>>> edgeSourceKeysMap = new HashMap<>();
+
+    public void put(IElementIdentifier identifier, GraphElementDeclaration graphElementDeclaration) {
+        graphElementDeclMap.put(identifier, graphElementDeclaration);
+    }
+
+    public void putVertexKey(VertexIdentifier identifier, List<List<String>> primaryKey) {
+        vertexKeyMap.put(identifier, primaryKey);
+    }
+
+    public void putEdgeKeys(EdgeIdentifier identifier, List<List<String>> sourceKey,
+            List<List<String>> destinationKey) {
+        edgeSourceKeysMap.put(identifier, sourceKey);
+        edgeDestKeysMap.put(identifier, destinationKey);
+    }
+
+    public GraphElementDeclaration getElementDecl(IElementIdentifier identifier) {
+        return graphElementDeclMap.get(identifier);
+    }
+
+    public List<List<String>> getVertexKey(VertexIdentifier identifier) {
+        return vertexKeyMap.get(identifier);
+    }
+
+    public List<List<String>> getEdgeDestKey(EdgeIdentifier identifier) {
+        return edgeDestKeysMap.get(identifier);
+    }
+
+    public List<List<String>> getEdgeSourceKey(EdgeIdentifier identifier) {
+        return edgeSourceKeysMap.get(identifier);
+    }
+
+    @Override
+    public Iterator<GraphElementDeclaration> iterator() {
+        return graphElementDeclMap.values().iterator();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/AliasLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/AliasLookupTable.java
new file mode 100644
index 0000000..d16e85d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/AliasLookupTable.java
@@ -0,0 +1,62 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+/**
+ * Lookup table for JOIN and ITERATION aliases, indexed by their representative (i.e. element) variables.
+ */
+public class AliasLookupTable {
+    private final GraphixDeepCopyVisitor deepCopyVisitor = new GraphixDeepCopyVisitor();
+    private final Map<VariableExpr, VariableExpr> joinAliasMap = new HashMap<>();
+    private final Map<VariableExpr, VariableExpr> iterationAliasMap = new HashMap<>();
+
+    public void addJoinAlias(VariableExpr elementVariable, VariableExpr aliasVariable) {
+        joinAliasMap.put(elementVariable, aliasVariable);
+    }
+
+    public void addIterationAlias(VariableExpr elementVariable, VariableExpr aliasVariable) {
+        iterationAliasMap.put(elementVariable, aliasVariable);
+    }
+
+    public VariableExpr getJoinAlias(VariableExpr elementVariable) throws CompilationException {
+        if (joinAliasMap.containsKey(elementVariable)) {
+            return deepCopyVisitor.visit(joinAliasMap.get(elementVariable), null);
+        }
+        return null;
+    }
+
+    public VariableExpr getIterationAlias(VariableExpr elementVariable) throws CompilationException {
+        if (iterationAliasMap.containsKey(elementVariable)) {
+            return deepCopyVisitor.visit(iterationAliasMap.get(elementVariable), null);
+        }
+        return null;
+    }
+
+    public void reset() {
+        joinAliasMap.clear();
+        iterationAliasMap.clear();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/EnvironmentActionFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/EnvironmentActionFactory.java
new file mode 100644
index 0000000..66f3f0e
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/EnvironmentActionFactory.java
@@ -0,0 +1,692 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower;
+
+import static org.apache.asterix.graphix.lang.rewrite.util.LowerRewritingUtil.buildAccessorList;
+import static org.apache.asterix.graphix.lang.rewrite.util.LowerRewritingUtil.buildVertexEdgeJoin;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
+import org.apache.asterix.graphix.lang.annotation.LoweringExemptAnnotation;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.AbstractInlineAction;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.IEnvironmentAction;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.MatchSemanticAction;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrite.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.VariableRemapCloneVisitor;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.TrueLiteral;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+
+/**
+ * Build {@link IEnvironmentAction} instances to manipulate a {@link LoweringEnvironment}.
+ */
+public class EnvironmentActionFactory {
+    private final Map<IElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
+    private final ElementLookupTable elementLookupTable;
+    private final AliasLookupTable aliasLookupTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final VariableRemapCloneVisitor remapCloneVisitor;
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
+
+    // The following must be provided before any creation methods are used.
+    private GraphIdentifier graphIdentifier;
+
+    public EnvironmentActionFactory(Map<IElementIdentifier, ElementBodyAnalysisContext> analysisContextMap,
+            ElementLookupTable elementLookupTable, AliasLookupTable aliasLookupTable,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.analysisContextMap = analysisContextMap;
+        this.elementLookupTable = elementLookupTable;
+        this.aliasLookupTable = aliasLookupTable;
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.remapCloneVisitor = new VariableRemapCloneVisitor(graphixRewritingContext);
+    }
+
+    public void reset(GraphIdentifier graphIdentifier) {
+        this.aliasLookupTable.reset();
+        this.graphIdentifier = graphIdentifier;
+    }
+
+    /**
+     * @see PathPatternAction
+     */
+    public IEnvironmentAction buildPathPatternAction(PathPatternExpr pathPatternExpr) {
+        return new PathPatternAction(pathPatternExpr);
+    }
+
+    /**
+     * @see MatchSemanticAction
+     */
+    public IEnvironmentAction buildMatchSemanticAction(FromGraphClause fromGraphClause) throws CompilationException {
+        return new MatchSemanticAction(graphixRewritingContext, fromGraphClause, aliasLookupTable);
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to introduce a WHERE clause into an environment with the given expression.
+     */
+    public IEnvironmentAction buildFilterExprAction(Expression filterExpr, VariableExpr elementVariable,
+            VariableExpr iterationVariable) throws CompilationException {
+        VariableExpr iterationVarCopy = graphixDeepCopyVisitor.visit(iterationVariable, null);
+        VariableExpr elementVarCopy = graphixDeepCopyVisitor.visit(elementVariable, null);
+        iterationVarCopy.setSourceLocation(filterExpr.getSourceLocation());
+        remapCloneVisitor.addSubstitution(elementVarCopy, iterationVarCopy);
+        return loweringEnvironment -> loweringEnvironment.acceptTransformer(lowerList -> {
+            ILangExpression remapCopyFilterExpr = remapCloneVisitor.substitute(filterExpr);
+            WhereClause filterWhereClause = new WhereClause((Expression) remapCopyFilterExpr);
+            filterWhereClause.setSourceLocation(filterExpr.getSourceLocation());
+            lowerList.addNonRepresentativeClause(filterWhereClause);
+            remapCloneVisitor.resetSubstitutions();
+        });
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle a dangling vertex / vertex that is (currently) disconnected.
+     * Even though we introduce CROSS-JOINs here, we will not actually perform this CROSS-JOIN if this is the first
+     * vertex we are lowering. There are three possible {@link IEnvironmentAction}s generated here:
+     * <ul>
+     *  <li>An action for inlined vertices that have no projections.</li>
+     *  <li>An action for inlined vertices with projections.</li>
+     *  <li>An action for non-inlined vertices.</li>
+     * </ul>
+     */
+    public IEnvironmentAction buildDanglingVertexAction(VertexPatternExpr vertexPatternExpr)
+            throws CompilationException {
+        if (vertexPatternExpr.findHint(LoweringExemptAnnotation.class) == LoweringExemptAnnotation.INSTANCE) {
+            return loweringEnvironment -> {
+            };
+        }
+        VariableExpr vertexVar = vertexPatternExpr.getVariableExpr();
+        VariableExpr iterationVar = graphixRewritingContext.getGraphixVariableCopy(vertexVar);
+        VariableExpr intermediateVar = graphixRewritingContext.getGraphixVariableCopy(vertexVar);
+
+        // We should only be working with one identifier (given that we only have one label).
+        List<VertexIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        VertexIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
+        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Introduce our iteration expression.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+                        VariableExpr iterationVarCopy = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy,
+                                null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                        joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                    }
+
+                    // Bind our intermediate (join) variable and vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        VariableExpr vertexVarCopy = graphixDeepCopyVisitor.visit(vertexVar, null);
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addVertexBinding(vertexVarCopy, iterationVarCopy2);
+                    });
+                    aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else if (vertexAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Introduce our iteration expression.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+                        VariableExpr iterationVarCopy = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy,
+                                null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                        joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                    }
+
+                    // Build a record constructor from our context to bind to our vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, recordConstructor1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addVertexBinding(vertexVar, recordConstructor2);
+                    });
+                    aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
+            return loweringEnvironment -> {
+                // Introduce our iteration expression.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    ILangExpression declBodyCopy = SqlppRewriteUtil.deepCopy(elementDeclaration.getNormalizedBody());
+                    VariableExpr iterationVarCopy = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, (Expression) declBodyCopy, iterationVarCopy,
+                            null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                    joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(joinClause);
+                });
+
+                // If we have a filter expression, add it as a WHERE clause here.
+                final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                if (filterExpr != null) {
+                    loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                }
+
+                // Bind our intermediate (join) variable and vertex variable.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                    LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                    lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                    lowerList.addVertexBinding(vertexVar, iterationVarCopy2);
+                });
+                aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle an edge that we can fold into (attach from) an already introduced
+     * vertex. A folded edge is implicitly inlined. There are two possible {@link IEnvironmentAction}s generated here:
+     * <ul>
+     *  <li>An action for inlined, folded edges that have no projections.</li>
+     *  <li>An action for inlined, folded edges that have projections.</li>
+     * </ul>
+     */
+    public IEnvironmentAction buildFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr) throws CompilationException {
+        VariableExpr vertexVar = vertexPatternExpr.getVariableExpr();
+        VariableExpr edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr();
+        VariableExpr intermediateVar = graphixRewritingContext.getGraphixVariableCopy(edgeVar);
+
+        // We should only be working with one identifier (given that we only have one label).
+        List<EdgeIdentifier> edgeElementIDs = edgePatternExpr.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        EdgeIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        if (edgeAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
+                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = edgePatternExpr.getEdgeDescriptor().getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, edgeVar, elementVariable));
+                    }
+
+                    // Build a binding for our edge variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr elementVarCopy1 = graphixDeepCopyVisitor.visit(elementVariable, null);
+                        VariableExpr elementVarCopy2 = graphixDeepCopyVisitor.visit(elementVariable, null);
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, elementVarCopy1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addEdgeBinding(edgeVar, elementVarCopy2);
+                    });
+                    aliasLookupTable.addIterationAlias(edgeVar, elementVariable);
+                    aliasLookupTable.addJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
+                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = edgePatternExpr.getEdgeDescriptor().getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, edgeVar, elementVariable));
+                    }
+
+                    // Build a record constructor from our context to bind to our edge and intermediate (join) var.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, recordConstructor1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addEdgeBinding(edgeVar, recordConstructor2);
+                    });
+                    aliasLookupTable.addIterationAlias(edgeVar, elementVariable);
+                    aliasLookupTable.addJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle an edge that we cannot fold into an already introduced vertex.
+     * There are three possible {@link IEnvironmentAction}s generated here:
+     * <ul>
+     *  <li>An action for inlined edges that have no projections.</li>
+     *  <li>An action for inlined edges that have projections.</li>
+     *  <li>An action for non-inlined edges.</li>
+     * </ul>
+     */
+    public IEnvironmentAction buildNonFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<EdgeIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VariableExpr edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr();
+        VariableExpr vertexVar = vertexPatternExpr.getVariableExpr();
+        VariableExpr iterationVar = graphixRewritingContext.getGraphixVariableCopy(edgeVar);
+        VariableExpr intermediateVar = graphixRewritingContext.getGraphixVariableCopy(edgeVar);
+
+        // We should only be working with one edge identifier...
+        List<EdgeIdentifier> edgeElementIDs = edgePatternExpr.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        EdgeIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        Expression datasetCallExpression = edgeAnalysisContext.getDatasetCallExpression();
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<VertexIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        VertexIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        if (edgeAnalysisContext.isExpressionInline() && edgeAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our edge iteration variable to our vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr vertexJoinExpr = aliasLookupTable.getJoinAlias(vertexVar);
+                        VariableExpr vertexVarCopy = graphixDeepCopyVisitor.visit(vertexJoinExpr, null);
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexVarCopy, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(iterationVarCopy1, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy2,
+                                null, vertexEdgeJoin, null);
+                        joinClause.setSourceLocation(edgePatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = edgePatternExpr.getEdgeDescriptor().getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, edgeVar, iterationVar));
+                    }
+
+                    // Bind our intermediate (join) variable and edge variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addEdgeBinding(edgeVar, iterationVarCopy2);
+                    });
+                    aliasLookupTable.addIterationAlias(edgeVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else if (edgeAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our edge iteration variable to our vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr vertexJoinExpr = aliasLookupTable.getJoinAlias(vertexVar);
+                        VariableExpr vertexVarCopy = graphixDeepCopyVisitor.visit(vertexJoinExpr, null);
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexVarCopy, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(iterationVarCopy1, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy2,
+                                null, vertexEdgeJoin, null);
+                        joinClause.setSourceLocation(edgePatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = edgePatternExpr.getEdgeDescriptor().getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, edgeVar, iterationVar));
+                    }
+
+                    // Build a record constructor from our context to bind to our edge variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, recordConstructor1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addEdgeBinding(edgeVar, recordConstructor2);
+                    });
+                    aliasLookupTable.addIterationAlias(edgeVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(edgeIdentifier);
+            return loweringEnvironment -> {
+                // Join our edge body to our vertex variable.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    VariableExpr vertexJoinExpr = aliasLookupTable.getJoinAlias(vertexVar);
+                    VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                            buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                            buildAccessorList(iterationVarCopy1, edgeKeyAccess.apply(edgeIdentifier)));
+                    ILangExpression declBodyCopy = SqlppRewriteUtil.deepCopy(elementDeclaration.getNormalizedBody());
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, (Expression) declBodyCopy, iterationVarCopy2,
+                            null, vertexEdgeJoin, null);
+                    joinClause.setSourceLocation(edgePatternExpr.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(joinClause);
+                });
+
+                // If we have a filter expression, add it as a WHERE clause here.
+                final Expression filterExpr = edgePatternExpr.getEdgeDescriptor().getFilterExpr();
+                if (filterExpr != null) {
+                    loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, edgeVar, iterationVar));
+                }
+
+                // Bind our intermediate (join) variable and edge variable.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                    LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                    lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                    lowerList.addEdgeBinding(edgeVar, iterationVarCopy2);
+                });
+                aliasLookupTable.addIterationAlias(edgeVar, iterationVar);
+                aliasLookupTable.addJoinAlias(edgeVar, intermediateVar);
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to introduce a WHERE-CLAUSE that will correlate a vertex and edge.
+     */
+    public IEnvironmentAction buildRawJoinVertexAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<EdgeIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VariableExpr edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr();
+        VariableExpr vertexVar = vertexPatternExpr.getVariableExpr();
+
+        // We should only be working with one edge identifier...
+        List<EdgeIdentifier> edgeElementIDs = edgePatternExpr.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        EdgeIdentifier edgeIdentifier = edgeElementIDs.get(0);
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<VertexIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        VertexIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        return loweringEnvironment -> {
+            // No aliases need to be introduced, we just need to add a WHERE-CONJUNCT.
+            VariableExpr edgeJoinExpr = aliasLookupTable.getJoinAlias(edgeVar);
+            VariableExpr vertexJoinExpr = aliasLookupTable.getJoinAlias(vertexVar);
+            VariableExpr edgeJoinExprCopy = graphixDeepCopyVisitor.visit(edgeJoinExpr, null);
+            VariableExpr vertexJoinExprCopy = graphixDeepCopyVisitor.visit(vertexJoinExpr, null);
+            loweringEnvironment.acceptTransformer(lowerList -> {
+                Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                        buildAccessorList(vertexJoinExprCopy, elementLookupTable.getVertexKey(vertexIdentifier)),
+                        buildAccessorList(edgeJoinExprCopy, edgeKeyAccess.apply(edgeIdentifier)));
+                WhereClause whereClause = new WhereClause(vertexEdgeJoin);
+                whereClause.setSourceLocation(edgePatternExpr.getSourceLocation());
+                lowerList.addNonRepresentativeClause(whereClause);
+            });
+        };
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle a vertex that is bound to an existing (already introduced) edge.
+     * There are three possible {@link IEnvironmentAction}s generated here:
+     * <ul>
+     *  <li>An action for inlined vertices that have no projections.</li>
+     *  <li>An action for inlined vertices that have projections.</li>
+     *  <li>An action for non-inlined vertices.</li>
+     * </ul>
+     */
+    public IEnvironmentAction buildBoundVertexAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<EdgeIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VariableExpr edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr();
+        VariableExpr vertexVar = vertexPatternExpr.getVariableExpr();
+        VariableExpr iterationVar = graphixRewritingContext.getGraphixVariableCopy(vertexVar);
+        VariableExpr intermediateVar = graphixRewritingContext.getGraphixVariableCopy(vertexVar);
+
+        // We should only be working with one edge identifier...
+        List<EdgeIdentifier> edgeElementIDs = edgePatternExpr.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        EdgeIdentifier edgeIdentifier = edgeElementIDs.get(0);
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<VertexIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        VertexIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
+        Expression datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our vertex iteration variable to our edge variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr edgeJoinExpr = aliasLookupTable.getJoinAlias(edgeVar);
+                        VariableExpr edgeJoinCopy = graphixDeepCopyVisitor.visit(edgeJoinExpr, null);
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(iterationVarCopy1, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(edgeJoinCopy, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy2,
+                                null, vertexEdgeJoin, null);
+                        joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                    }
+
+                    // Bind our intermediate (join) variable and vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addVertexBinding(vertexVar, iterationVarCopy2);
+                    });
+                    aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else if (vertexAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our vertex iteration variable to our edge variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr edgeJoinExpr = aliasLookupTable.getJoinAlias(edgeVar);
+                        VariableExpr edgeJoinCopy = graphixDeepCopyVisitor.visit(edgeJoinExpr, null);
+                        VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(iterationVarCopy1, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(edgeJoinCopy, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression, iterationVarCopy2,
+                                null, vertexEdgeJoin, null);
+                        joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                        lowerList.addNonRepresentativeClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // If we have a filter expression, add it as a WHERE clause here.
+                    final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                    if (filterExpr != null) {
+                        loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                    }
+
+                    // Build a record constructor from our context to bind to our vertex variable.
+                    loweringEnvironment.acceptTransformer(lowerList -> {
+                        VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, recordConstructor1);
+                        lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                        lowerList.addVertexBinding(vertexVar, recordConstructor2);
+                    });
+                    aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
+            return loweringEnvironment -> {
+                // Join our vertex body to our edge variable.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    VariableExpr edgeJoinExpr = aliasLookupTable.getJoinAlias(edgeVar);
+                    VariableExpr edgeJoinCopy = graphixDeepCopyVisitor.visit(edgeJoinExpr, null);
+                    VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                            buildAccessorList(iterationVarCopy1, elementLookupTable.getVertexKey(vertexIdentifier)),
+                            buildAccessorList(edgeJoinCopy, edgeKeyAccess.apply(edgeIdentifier)));
+                    ILangExpression declBodyCopy = SqlppRewriteUtil.deepCopy(elementDeclaration.getNormalizedBody());
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, (Expression) declBodyCopy, iterationVarCopy2,
+                            null, vertexEdgeJoin, null);
+                    joinClause.setSourceLocation(vertexPatternExpr.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(joinClause);
+                });
+
+                // If we have a filter expression, add it as a WHERE clause here.
+                final Expression filterExpr = vertexPatternExpr.getFilterExpr();
+                if (filterExpr != null) {
+                    loweringEnvironment.acceptAction(buildFilterExprAction(filterExpr, vertexVar, iterationVar));
+                }
+
+                // Bind our intermediate (join) variable and vertex variable.
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    VariableExpr iterationVarCopy1 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr iterationVarCopy2 = graphixDeepCopyVisitor.visit(iterationVar, null);
+                    VariableExpr intermediateVarCopy = graphixDeepCopyVisitor.visit(intermediateVar, null);
+                    LetClause nonRepresentativeBinding = new LetClause(intermediateVarCopy, iterationVarCopy1);
+                    lowerList.addNonRepresentativeClause(nonRepresentativeBinding);
+                    lowerList.addVertexBinding(vertexVar, iterationVarCopy2);
+                });
+                aliasLookupTable.addIterationAlias(vertexVar, iterationVar);
+                aliasLookupTable.addJoinAlias(vertexVar, intermediateVar);
+            };
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/LoweringEnvironment.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/LoweringEnvironment.java
new file mode 100644
index 0000000..ea80458
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/LoweringEnvironment.java
@@ -0,0 +1,366 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause.ClauseInputEnvironment;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause.ClauseOutputEnvironment;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.IEnvironmentAction;
+import org.apache.asterix.graphix.lang.rewrite.lower.action.ILowerListTransformer;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.CollectionTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.StateContainer;
+import org.apache.asterix.graphix.lang.rewrite.util.LowerRewritingUtil;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixLoweringVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.VariableRemapCloneVisitor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
+import org.apache.asterix.lang.common.base.Literal;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.TrueLiteral;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateWithConditionClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.FreeVariableVisitor;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * @see GraphixLoweringVisitor
+ */
+public class LoweringEnvironment {
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
+    private final ClauseCollection mainClauseCollection;
+    private final GraphIdentifier graphIdentifier;
+    private final SourceLocation sourceLocation;
+
+    // The following are created through beginLeftMatch / beginTempLowerList / beginBranches.
+    private ClauseCollection leftClauseCollection;
+    private ClauseCollection tempClauseCollection;
+    private ClauseCollection branchClauseCollection;
+    private CollectionTable collectionTable;
+    private boolean isInlineLegal;
+
+    public LoweringEnvironment(GraphixRewritingContext graphixRewritingContext, GraphIdentifier graphIdentifier,
+            SourceLocation sourceLocation) {
+        this.mainClauseCollection = new ClauseCollection(sourceLocation);
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.graphIdentifier = graphIdentifier;
+        this.sourceLocation = sourceLocation;
+        this.leftClauseCollection = null;
+        this.tempClauseCollection = null;
+        this.branchClauseCollection = null;
+    }
+
+    public GraphIdentifier getGraphIdentifier() {
+        return graphIdentifier;
+    }
+
+    public void acceptAction(IEnvironmentAction environmentAction) throws CompilationException {
+        environmentAction.apply(this);
+    }
+
+    public void acceptTransformer(ILowerListTransformer sequenceTransformer) throws CompilationException {
+        // Fixed point lowering will always take precedence.
+        sequenceTransformer.accept(Objects.requireNonNullElseGet(tempClauseCollection,
+                () -> Objects.requireNonNullElseGet(branchClauseCollection,
+                        () -> Objects.requireNonNullElse(leftClauseCollection, mainClauseCollection))));
+    }
+
+    public void setInlineLegal(boolean isInlineLegal) {
+        this.isInlineLegal = isInlineLegal;
+    }
+
+    public boolean isInlineLegal() {
+        return isInlineLegal;
+    }
+
+    public void beginLeftMatch() throws CompilationException {
+        if (leftClauseCollection != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "LEFT-MATCH lowering is currently in progress!");
+        }
+        leftClauseCollection = new ClauseCollection(sourceLocation);
+    }
+
+    public void endLeftMatch() throws CompilationException {
+        if (leftClauseCollection.getNonRepresentativeClauses().isEmpty()) {
+            // This is an extraneous LEFT-MATCH. Do not modify anything.
+            leftClauseCollection = null;
+            return;
+        }
+
+        // Build our substitution visitor and environment.
+        VariableRemapCloneVisitor remapCloneVisitor = new VariableRemapCloneVisitor(graphixRewritingContext);
+        VariableExpr nestingVariable = graphixRewritingContext.getGraphixVariableCopy("_LeftMatch");
+        final Consumer<VariableExpr> substitutionAdder = v -> {
+            VariableExpr nestingVariableCopy = new VariableExpr(nestingVariable.getVar());
+            FieldAccessor fieldAccessor = new FieldAccessor(nestingVariableCopy, v.getVar());
+            remapCloneVisitor.addSubstitution(v, fieldAccessor);
+        };
+
+        // Build up our projection list.
+        List<Projection> projectionList = new ArrayList<>();
+        List<AbstractClause> leftLowerClauses = leftClauseCollection.getNonRepresentativeClauses();
+        for (AbstractClause workingClause : leftLowerClauses) {
+            if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                continue;
+            }
+
+            // Identify our right variable.
+            VariableExpr rightVariable;
+            if (workingClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                rightVariable = ((LetClause) workingClause).getVarExpr();
+
+            } else if (workingClause instanceof AbstractBinaryCorrelateClause) {
+                rightVariable = ((AbstractBinaryCorrelateClause) workingClause).getRightVariable();
+
+            } else {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Illegal clause found!");
+            }
+            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, rightVariable,
+                    SqlppVariableUtil.toUserDefinedVariableName(rightVariable.getVar()).getValue()));
+            substitutionAdder.accept(rightVariable);
+        }
+
+        // Nestle our clauses in a SELECT-BLOCK.
+        LowerListClause leftLowerClause = new LowerListClause(leftClauseCollection);
+        SelectClause selectClause = new SelectClause(null, new SelectRegular(projectionList), false);
+        SelectBlock selectBlock = new SelectBlock(selectClause, null, null, null, null);
+        selectBlock.setFromClause(new FromGraphClause(leftLowerClause));
+        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
+        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
+        SelectExpression selectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
+
+        // Merge the collection we just built with our main sequence.
+        IVisitorExtension visitorExtension = leftLowerClause.getVisitorExtension();
+        Expression conditionExpression = generateJoinCondition(leftLowerClauses.listIterator(), visitorExtension);
+        VariableExpr nestingVariableCopy = graphixDeepCopyVisitor.visit(nestingVariable, null);
+        JoinClause leftJoinClause = new JoinClause(JoinType.LEFTOUTER, selectExpression, nestingVariableCopy, null,
+                (Expression) remapCloneVisitor.substitute(conditionExpression), Literal.Type.MISSING);
+        mainClauseCollection.addNonRepresentativeClause(leftJoinClause);
+
+        // Introduce our representative variables back into our main sequence.
+        for (LetClause representativeVertexBinding : leftClauseCollection.getRepresentativeVertexBindings()) {
+            VariableExpr representativeVariable = representativeVertexBinding.getVarExpr();
+            VariableExpr representativeVariableCopy = graphixDeepCopyVisitor.visit(representativeVariable, null);
+            Expression rightExpression = representativeVertexBinding.getBindingExpr();
+            Expression reboundExpression = (Expression) remapCloneVisitor.substitute(rightExpression);
+            mainClauseCollection.addVertexBinding(representativeVariableCopy, reboundExpression);
+        }
+        for (LetClause representativeEdgeBinding : leftClauseCollection.getRepresentativeEdgeBindings()) {
+            VariableExpr representativeVariable = representativeEdgeBinding.getVarExpr();
+            VariableExpr representativeVariableCopy = graphixDeepCopyVisitor.visit(representativeVariable, null);
+            Expression rightExpression = representativeEdgeBinding.getBindingExpr();
+            Expression reboundExpression = (Expression) remapCloneVisitor.substitute(rightExpression);
+            mainClauseCollection.addEdgeBinding(representativeVariableCopy, reboundExpression);
+        }
+        for (LetClause representativePathBinding : leftClauseCollection.getRepresentativePathBindings()) {
+            VariableExpr representativeVariable = representativePathBinding.getVarExpr();
+            VariableExpr representativeVariableCopy = graphixDeepCopyVisitor.visit(representativeVariable, null);
+            Expression rightExpression = representativePathBinding.getBindingExpr();
+            Expression reboundExpression = (Expression) remapCloneVisitor.substitute(rightExpression);
+            mainClauseCollection.addPathBinding(representativeVariableCopy, reboundExpression);
+        }
+
+        // Do not reintroduce our vertex, edge, and path bindings.
+        leftClauseCollection.getRepresentativeVertexBindings().clear();
+        leftClauseCollection.getRepresentativeEdgeBindings().clear();
+        leftClauseCollection.getRepresentativePathBindings().clear();
+        leftClauseCollection = null;
+    }
+
+    public void beginTempLowerList() throws CompilationException {
+        if (tempClauseCollection != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Temp branch lowering is currently in progress!");
+        }
+        tempClauseCollection = new ClauseCollection(sourceLocation);
+    }
+
+    public void endTempLowerList() {
+        // We discard the collection we just built.
+        tempClauseCollection = null;
+    }
+
+    public void beginBranches() throws CompilationException {
+        if (collectionTable != null || branchClauseCollection != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Path branch lowering is currently in progress!");
+        }
+        collectionTable = new CollectionTable();
+        branchClauseCollection = new ClauseCollection(sourceLocation);
+    }
+
+    public void flushBranch(EdgePatternExpr edgePatternExpr, boolean isJoiningLeftToRight) throws CompilationException {
+        if (branchClauseCollection.getRepresentativeVertexBindings().size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Only one vertex should exist in the clause collection!");
+        }
+        if (branchClauseCollection.getRepresentativeEdgeBindings().size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Only one edge should exist in the clause collection!");
+        }
+        collectionTable.putCollection(edgePatternExpr, isJoiningLeftToRight, branchClauseCollection);
+        branchClauseCollection = new ClauseCollection(sourceLocation);
+    }
+
+    public void endBranches(ClauseOutputEnvironment clauseOutputEnvironment,
+            ClauseInputEnvironment clauseInputEnvironment, Map<ElementLabel, VariableExpr> inputMap,
+            Map<ElementLabel, VariableExpr> outputMap, AliasLookupTable aliasLookupTable, SourceLocation sourceLocation)
+            throws CompilationException {
+        // Build the input map for our collection table.
+        Map<ElementLabel, StateContainer> inputStateMap = new HashMap<>();
+        for (Map.Entry<ElementLabel, VariableExpr> mapEntry : inputMap.entrySet()) {
+            VariableExpr iterationAlias = aliasLookupTable.getIterationAlias(mapEntry.getValue());
+            VariableExpr joinAlias = aliasLookupTable.getJoinAlias(mapEntry.getValue());
+            inputStateMap.put(mapEntry.getKey(), new StateContainer(iterationAlias, joinAlias));
+        }
+        collectionTable.setInputMap(inputStateMap);
+
+        // ...and the output map for out collection table.
+        Map<ElementLabel, StateContainer> outputStateMap = new HashMap<>();
+        for (Map.Entry<ElementLabel, VariableExpr> mapEntry : outputMap.entrySet()) {
+            VariableExpr iterationAlias = aliasLookupTable.getIterationAlias(mapEntry.getValue());
+            VariableExpr joinAlias = aliasLookupTable.getJoinAlias(mapEntry.getValue());
+            outputStateMap.put(mapEntry.getKey(), new StateContainer(iterationAlias, joinAlias));
+        }
+        collectionTable.setOutputMap(outputStateMap);
+
+        // Add our GRAPH-CLAUSE to our main sequence.
+        LowerSwitchClause lowerSwitchClause =
+                new LowerSwitchClause(collectionTable, clauseInputEnvironment, clauseOutputEnvironment);
+        lowerSwitchClause.setSourceLocation(sourceLocation);
+        mainClauseCollection.addNonRepresentativeClause(lowerSwitchClause);
+        branchClauseCollection = null;
+        collectionTable = null;
+    }
+
+    public void endLowering(FromGraphClause targetFromClause) {
+        targetFromClause.setLowerClause(new LowerListClause(mainClauseCollection));
+    }
+
+    private static Expression generateJoinCondition(ListIterator<AbstractClause> lowerClauseIterator,
+            IVisitorExtension visitorExtension) throws CompilationException {
+        final List<Expression> joinConditionExpressions = new ArrayList<>();
+        final Collection<VariableExpr> freeVariables = new HashSet<>();
+        final FreeVariableVisitor freeVariableVisitor = new FreeVariableVisitor() {
+            @Override
+            public Void visit(IVisitorExtension visitorExtension, Collection<VariableExpr> freeVars)
+                    throws CompilationException {
+                Collection<VariableExpr> bindingVariables = new HashSet<>();
+                Collection<VariableExpr> conditionFreeVars = new HashSet<>();
+                Collection<VariableExpr> clauseFreeVars = new HashSet<>();
+                while (lowerClauseIterator.hasNext()) {
+                    AbstractClause lowerClause = lowerClauseIterator.next();
+                    clauseFreeVars.clear();
+                    if (lowerClause instanceof AbstractBinaryCorrelateClause) {
+                        AbstractBinaryCorrelateClause correlateClause = (AbstractBinaryCorrelateClause) lowerClause;
+                        correlateClause.getRightExpression().accept(this, clauseFreeVars);
+                        if (lowerClause.getClauseType() == Clause.ClauseType.UNNEST_CLAUSE) {
+                            clauseFreeVars.removeAll(bindingVariables);
+                            if (!clauseFreeVars.isEmpty()) {
+                                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                        "Encountered UNNEST-CLAUSE with free variables.");
+                            }
+
+                        } else {
+                            AbstractBinaryCorrelateWithConditionClause clauseWithCondition =
+                                    (AbstractBinaryCorrelateWithConditionClause) correlateClause;
+                            conditionFreeVars.clear();
+                            clauseWithCondition.getConditionExpression().accept(this, conditionFreeVars);
+                            conditionFreeVars.removeAll(bindingVariables);
+                            conditionFreeVars.remove(correlateClause.getRightVariable());
+                            if (!conditionFreeVars.isEmpty()) {
+                                // We have found a JOIN with a free variable.
+                                joinConditionExpressions.add(clauseWithCondition.getConditionExpression());
+                                clauseWithCondition.setConditionExpression(new LiteralExpr(TrueLiteral.INSTANCE));
+                            }
+                            clauseFreeVars.addAll(conditionFreeVars);
+                        }
+
+                        // Adds binding variables.
+                        bindingVariables.add(correlateClause.getRightVariable());
+                        freeVars.addAll(clauseFreeVars);
+
+                    } else if (lowerClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                        WhereClause whereClause = (WhereClause) lowerClause;
+                        whereClause.getWhereExpr().accept(this, clauseFreeVars);
+                        clauseFreeVars.removeAll(bindingVariables);
+                        if (!clauseFreeVars.isEmpty()) {
+                            joinConditionExpressions.add(whereClause.getWhereExpr());
+                            lowerClauseIterator.remove();
+                        }
+                        freeVars.addAll(clauseFreeVars);
+
+                    } else if (lowerClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) lowerClause;
+                        letClause.getBindingExpr().accept(this, clauseFreeVars);
+                        clauseFreeVars.removeAll(bindingVariables);
+                        bindingVariables.add(letClause.getVarExpr());
+                        if (!clauseFreeVars.isEmpty()) {
+                            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                    "Encountered LET-CLAUSE with free variables.");
+                        }
+                    }
+                }
+                return null;
+            }
+        };
+        freeVariableVisitor.visit(visitorExtension, freeVariables);
+        return joinConditionExpressions.isEmpty() ? new LiteralExpr(TrueLiteral.INSTANCE)
+                : LowerRewritingUtil.buildConnectedClauses(joinConditionExpressions, OperatorType.AND);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/AbstractInlineAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/AbstractInlineAction.java
new file mode 100644
index 0000000..da91946
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/AbstractInlineAction.java
@@ -0,0 +1,151 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower.action;
+
+import static org.apache.asterix.graphix.lang.rewrite.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.VariableRemapCloneVisitor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.FieldBinding;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.StringLiteral;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+
+/**
+ * Inline an element body into a {@link LoweringEnvironment}. This includes:
+ * <ol>
+ *  <li>Copying {@link UnnestClause}, {@link LetClause}, and {@link WhereClause} AST nodes from our body analysis</li>
+ *  <li>Creating {@link RecordConstructor} AST nodes to inline
+ *  {@link org.apache.asterix.lang.sqlpp.clause.SelectRegular} nodes.</li>
+ * </ol>
+ */
+public abstract class AbstractInlineAction implements IEnvironmentAction {
+    protected final GraphixRewritingContext graphixRewritingContext;
+    protected final ElementBodyAnalysisContext bodyAnalysisContext;
+    protected final VariableRemapCloneVisitor remapCloneVisitor;
+    protected final GraphixDeepCopyVisitor deepCopyVisitor;
+
+    // This may be mutated by our child.
+    protected VariableExpr elementVariable;
+
+    protected AbstractInlineAction(GraphixRewritingContext graphixRewritingContext,
+            ElementBodyAnalysisContext bodyAnalysisContext, VariableExpr elementVariable) {
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.bodyAnalysisContext = bodyAnalysisContext;
+        this.elementVariable = elementVariable;
+        this.remapCloneVisitor = new VariableRemapCloneVisitor(graphixRewritingContext);
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
+    }
+
+    @Override
+    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+        final Function<VariableExpr, VariableExpr> substitutionAdder = v -> {
+            VariableExpr reboundVariableExpr = graphixRewritingContext.getGraphixVariableCopy(v);
+            remapCloneVisitor.addSubstitution(v, reboundVariableExpr);
+            return reboundVariableExpr;
+        };
+
+        // To inline, we need to ensure that we substitute variables accordingly.
+        remapCloneVisitor.resetSubstitutions();
+        if (bodyAnalysisContext.getFromTermVariable() != null) {
+            VariableExpr fromTermVariableExpr = bodyAnalysisContext.getFromTermVariable();
+            VariableExpr elementVariableExpr = new VariableExpr(elementVariable.getVar());
+            remapCloneVisitor.addSubstitution(fromTermVariableExpr, elementVariableExpr);
+        }
+
+        // If we have any UNNEST clauses, we need to add these.
+        if (bodyAnalysisContext.getUnnestClauses() != null) {
+            for (AbstractBinaryCorrelateClause unnestClause : bodyAnalysisContext.getUnnestClauses()) {
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    UnnestClause copiedClause = (UnnestClause) remapCloneVisitor.substitute(unnestClause);
+                    if (copiedClause.hasPositionalVariable()) {
+                        substitutionAdder.apply(copiedClause.getPositionalVariable());
+                    }
+                    VariableExpr reboundUnnestVariable = substitutionAdder.apply(copiedClause.getRightVariable());
+                    UnnestClause newUnnestClause =
+                            new UnnestClause(copiedClause.getUnnestType(), copiedClause.getRightExpression(),
+                                    reboundUnnestVariable, null, copiedClause.getOuterUnnestMissingValueType());
+                    newUnnestClause.setSourceLocation(unnestClause.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(newUnnestClause);
+                });
+            }
+        }
+
+        // If we have any LET clauses, we need to substitute them in our WHERE and SELECT clauses.
+        if (bodyAnalysisContext.getLetClauses() != null) {
+            for (LetClause letClause : bodyAnalysisContext.getLetClauses()) {
+                // Remap this LET-CLAUSE to include our new variables. Move this to our correlated clauses.
+                LetClause copiedClause = (LetClause) remapCloneVisitor.substitute(letClause);
+                VariableExpr reboundLetVariable = substitutionAdder.apply(copiedClause.getVarExpr());
+                VariableExpr reboundLetVariableCopy = deepCopyVisitor.visit(reboundLetVariable, null);
+                Expression copiedBindingExpr = copiedClause.getBindingExpr();
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    LetClause reboundLetClause = new LetClause(reboundLetVariableCopy, copiedBindingExpr);
+                    reboundLetClause.setSourceLocation(letClause.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(reboundLetClause);
+                });
+            }
+        }
+
+        // If we have any WHERE clauses, we need to add these.
+        if (bodyAnalysisContext.getWhereClauses() != null) {
+            for (WhereClause whereClause : bodyAnalysisContext.getWhereClauses()) {
+                WhereClause copiedClause = (WhereClause) remapCloneVisitor.substitute(whereClause);
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    WhereClause newWhereClause = new WhereClause(copiedClause.getWhereExpr());
+                    newWhereClause.setSourceLocation(whereClause.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(newWhereClause);
+                });
+            }
+        }
+    }
+
+    protected RecordConstructor buildRecordConstructor() throws CompilationException {
+        if (bodyAnalysisContext.getSelectClauseProjections() != null) {
+            // Map our original variable to our element variable.
+            List<FieldBinding> fieldBindings = new ArrayList<>();
+            for (Projection projection : bodyAnalysisContext.getSelectClauseProjections()) {
+                LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(projection.getName()));
+                ILangExpression fieldValueExpr = remapCloneVisitor.substitute(projection.getExpression());
+                fieldBindings.add(new FieldBinding(fieldNameExpr, (Expression) fieldValueExpr));
+            }
+            return new RecordConstructor(fieldBindings);
+
+        } else {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Non-inlineable SELECT clause encountered, but was body was marked as inline!");
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/IEnvironmentAction.java
similarity index 87%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/IEnvironmentAction.java
index 4ab6030..90926bc 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/IEnvironmentAction.java
@@ -16,10 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.action;
+package org.apache.asterix.graphix.lang.rewrite.lower.action;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrite.lower.LoweringEnvironment;
 
 @FunctionalInterface
 public interface IEnvironmentAction {
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/ILowerListTransformer.java
similarity index 77%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/ILowerListTransformer.java
index 4509792..72a48f4 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/ILowerListTransformer.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.lang.rewrite.lower.action;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
 
 @FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+public interface ILowerListTransformer {
+    void accept(ClauseCollection lowerList) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/MatchSemanticAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/MatchSemanticAction.java
new file mode 100644
index 0000000..0fa6483
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/MatchSemanticAction.java
@@ -0,0 +1,332 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower.action;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsNavigationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SemanticsPatternOption;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.lower.AliasLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.rewrite.visitor.VariableRemapCloneVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+
+/**
+ * Define the semantics of evaluating a basic graph pattern query (i.e. how much isomorphism do we enforce), and b)
+ * b) the semantics of navigating between vertices (i.e. what type of uniqueness in the path should be enforced). We
+ * assume that all elements are named at this point and that our {@link FromGraphClause} is in canonical form.
+ * <p>
+ * We enforce the following basic graph pattern query semantics (by default, we enforce total isomorphism):
+ * <ul>
+ *  <li>For total isomorphism, no vertex and no edge can appear more than once across all {@link MatchClause}
+ *  nodes.</li>
+ *  <li>For vertex-isomorphism, we enforce that no vertex can appear more than once across all {@link MatchClause}
+ *  nodes.</li>
+ *  <li>For edge-isomorphism, we enforce that no edge can appear more than once across all {@link MatchClause}
+ *  nodes.</li>
+ *  <li>For homomorphism, we enforce nothing. Edge adjacency is already implicitly preserved.</li>
+ * </ul>
+ * <p>
+ * We enforce the following navigation query semantics (by default, we enforce no-repeat-anything):
+ * <ul>
+ *  <li>For no-repeat-vertices, no vertex instance can appear more than once in a path instance.</li>
+ *  <li>For no-repeat-edges, no edge instance can appear more than once in a path instance.</li>
+ *  <li>For no-repeat-anything, no vertex or edge instance can appear more than once in a path instance.</li>
+ * </ul>
+ */
+public class MatchSemanticAction implements IEnvironmentAction {
+    // We will walk through our FROM-GRAPH-CLAUSE and determine our isomorphism conjuncts.
+    private final SemanticsNavigationOption navigationSemantics;
+    private final SemanticsPatternOption patternSemantics;
+    private final FromGraphClause fromGraphClause;
+    private final AliasLookupTable aliasLookupTable;
+    private final VariableRemapCloneVisitor remapCloneVisitor;
+    private final GraphixDeepCopyVisitor deepCopyVisitor;
+
+    public MatchSemanticAction(GraphixRewritingContext graphixRewritingContext, FromGraphClause fromGraphClause,
+            AliasLookupTable aliasLookupTable) throws CompilationException {
+        this.fromGraphClause = fromGraphClause;
+        this.aliasLookupTable = aliasLookupTable;
+        this.remapCloneVisitor = new VariableRemapCloneVisitor(graphixRewritingContext);
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
+
+        // Determine our BGP query semantics.
+        String patternConfigKeyName = SemanticsPatternOption.OPTION_KEY_NAME;
+        this.patternSemantics = (SemanticsPatternOption) graphixRewritingContext.getSetting(patternConfigKeyName);
+
+        // Determine our navigation query semantics.
+        String navConfigKeyName = SemanticsNavigationOption.OPTION_KEY_NAME;
+        this.navigationSemantics = (SemanticsNavigationOption) graphixRewritingContext.getSetting(navConfigKeyName);
+    }
+
+    private static List<OperatorExpr> generateIsomorphismConjuncts(List<VariableExpr> variableList) {
+        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
+
+        // Find all unique pairs from our list of variables.
+        for (int i = 0; i < variableList.size(); i++) {
+            for (int j = i + 1; j < variableList.size(); j++) {
+                OperatorExpr inequalityConjunct = new OperatorExpr();
+                inequalityConjunct.addOperator(OperatorType.NEQ);
+                inequalityConjunct.addOperand(variableList.get(i));
+                inequalityConjunct.addOperand(variableList.get(j));
+                isomorphismConjuncts.add(inequalityConjunct);
+            }
+        }
+
+        return isomorphismConjuncts;
+    }
+
+    @Override
+    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+        Map<ElementLabel, List<VariableExpr>> vertexVariableMap = new HashMap<>();
+        Map<ElementLabel, List<VariableExpr>> edgeVariableMap = new HashMap<>();
+
+        // Populate the collections above.
+        fromGraphClause.accept(new AbstractGraphixQueryVisitor() {
+            private void populateVariableMap(ElementLabel label, VariableExpr variableExpr,
+                    Map<ElementLabel, List<VariableExpr>> labelVariableMap) {
+                Function<VariableExpr, Boolean> mapMatchFinder = v -> {
+                    final String variableName = SqlppVariableUtil.toUserDefinedName(variableExpr.getVar().getValue());
+                    return variableName.equals(SqlppVariableUtil.toUserDefinedName(v.getVar().getValue()));
+                };
+                if (labelVariableMap.containsKey(label)) {
+                    if (labelVariableMap.get(label).stream().noneMatch(mapMatchFinder::apply)) {
+                        labelVariableMap.get(label).add(variableExpr);
+                    }
+
+                } else {
+                    List<VariableExpr> variableList = new ArrayList<>();
+                    variableList.add(variableExpr);
+                    labelVariableMap.put(label, variableList);
+                }
+            }
+
+            @Override
+            public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+                // We only want to explore the top level of our FROM-GRAPH-CLAUSEs.
+                for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+                    matchClause.accept(this, arg);
+                }
+                return null;
+            }
+
+            @Override
+            public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg)
+                    throws CompilationException {
+                VariableExpr vertexVariable = vertexPatternExpr.getVariableExpr();
+                VariableExpr iterationVariable = aliasLookupTable.getIterationAlias(vertexVariable);
+                ElementLabel elementLabel = vertexPatternExpr.getLabels().iterator().next();
+                iterationVariable.setSourceLocation(vertexVariable.getSourceLocation());
+                populateVariableMap(elementLabel, iterationVariable, vertexVariableMap);
+                return null;
+            }
+
+            @Override
+            public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                VariableExpr edgeVariable = edgeDescriptor.getVariableExpr();
+                VariableExpr iterationVariable = aliasLookupTable.getIterationAlias(edgeVariable);
+                ElementLabel elementLabel = edgeDescriptor.getEdgeLabels().iterator().next();
+                iterationVariable.setSourceLocation(edgeVariable.getSourceLocation());
+                populateVariableMap(elementLabel, iterationVariable, edgeVariableMap);
+                return null;
+            }
+        }, null);
+
+        // Construct our isomorphism conjuncts.
+        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
+        if (patternSemantics == SemanticsPatternOption.ISOMORPHISM
+                || patternSemantics == SemanticsPatternOption.VERTEX_ISOMORPHISM) {
+            vertexVariableMap.values().stream().map(MatchSemanticAction::generateIsomorphismConjuncts)
+                    .forEach(isomorphismConjuncts::addAll);
+        }
+        if (patternSemantics == SemanticsPatternOption.ISOMORPHISM
+                || patternSemantics == SemanticsPatternOption.EDGE_ISOMORPHISM) {
+            edgeVariableMap.values().stream().map(MatchSemanticAction::generateIsomorphismConjuncts)
+                    .forEach(isomorphismConjuncts::addAll);
+        }
+
+        // Iterate through our clause sequence.
+        remapCloneVisitor.resetSubstitutions();
+        loweringEnvironment.acceptTransformer(new ILowerListTransformer() {
+            private final Set<VariableExpr> visitedVariables = new HashSet<>();
+
+            @Override
+            public void accept(ClauseCollection lowerList) throws CompilationException {
+                List<AbstractClause> nonRepresentativeClauses = lowerList.getNonRepresentativeClauses();
+                ListIterator<AbstractClause> clauseIterator = nonRepresentativeClauses.listIterator();
+                while (clauseIterator.hasNext()) {
+                    AbstractClause workingClause = clauseIterator.next();
+                    if (workingClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) workingClause;
+                        visitedVariables.add(letClause.getVarExpr());
+
+                    } else if (workingClause.getClauseType() == Clause.ClauseType.EXTENSION) {
+                        // Navigation semantics will be introduced at the plan translator.
+                        LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) workingClause;
+                        lowerSwitchClause.setNavigationSemantics(navigationSemantics);
+
+                    } else if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                        continue;
+
+                    } else {
+                        AbstractBinaryCorrelateClause correlateClause = (AbstractBinaryCorrelateClause) workingClause;
+                        visitedVariables.add(correlateClause.getRightVariable());
+
+                        // If we encounter a LEFT-JOIN, then we have created a LEFT-MATCH branch.
+                        if (workingClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
+                            JoinClause joinClause = (JoinClause) workingClause;
+                            if (joinClause.getJoinType() == JoinType.LEFTOUTER) {
+                                acceptLeftMatchJoin(clauseIterator, joinClause);
+                            }
+                        }
+                    }
+
+                    // Only introduce our conjunct if we have visited both variables (eagerly).
+                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
+                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
+                        List<Expression> operandList = isomorphismConjunct.getExprList();
+                        VariableExpr termVariable1 = ((VariableExpr) operandList.get(0));
+                        VariableExpr termVariable2 = ((VariableExpr) operandList.get(1));
+                        if (visitedVariables.contains(termVariable1) && visitedVariables.contains(termVariable2)) {
+                            clauseIterator.add(new WhereClause(isomorphismConjunct));
+                            appliedIsomorphismConjuncts.add(isomorphismConjunct);
+                        }
+                    }
+                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
+                }
+            }
+
+            private void acceptLeftMatchJoin(ListIterator<AbstractClause> clauseIterator, JoinClause joinClause)
+                    throws CompilationException {
+                // We can make the following assumptions about our JOIN here (i.e. the casts here are valid).
+                Expression rightExpression = joinClause.getRightExpression();
+                SelectExpression selectExpression = (SelectExpression) rightExpression;
+                SelectSetOperation selectSetOperation = selectExpression.getSelectSetOperation();
+                SelectBlock selectBlock = selectSetOperation.getLeftInput().getSelectBlock();
+                FromGraphClause fromGraphClause = (FromGraphClause) selectBlock.getFromClause();
+                LowerListClause lowerClause = (LowerListClause) fromGraphClause.getLowerClause();
+                Set<VariableExpr> localLiveVariables = new HashSet<>();
+                ListIterator<AbstractClause> leftClauseIterator =
+                        lowerClause.getClauseCollection().getNonRepresentativeClauses().listIterator();
+                while (leftClauseIterator.hasNext()) {
+                    AbstractClause workingClause = leftClauseIterator.next();
+                    VariableExpr rightVariable;
+                    if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                        continue;
+
+                    } else if (workingClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        rightVariable = ((LetClause) workingClause).getVarExpr();
+
+                    } else {
+                        rightVariable = ((AbstractBinaryCorrelateClause) workingClause).getRightVariable();
+                    }
+
+                    // Add our isomorphism conjunct to our main iterator.
+                    localLiveVariables.add(rightVariable);
+                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
+                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
+                        List<Expression> operandList = isomorphismConjunct.getExprList();
+                        VariableExpr termVariable1 = ((VariableExpr) operandList.get(0));
+                        VariableExpr termVariable2 = ((VariableExpr) operandList.get(1));
+                        VariableExpr leftVariable;
+                        if (termVariable1.equals(rightVariable)) {
+                            leftVariable = termVariable2;
+
+                        } else if (termVariable2.equals(rightVariable)) {
+                            leftVariable = termVariable1;
+
+                        } else {
+                            continue;
+                        }
+
+                        // Is our left variable introduced in our LEFT-CLAUSE-COLLECTION? Add the conjunct here.
+                        if (localLiveVariables.contains(leftVariable)) {
+                            leftClauseIterator.add(new WhereClause(isomorphismConjunct));
+                            appliedIsomorphismConjuncts.add(isomorphismConjunct);
+                            visitedVariables.add(rightVariable);
+                            continue;
+                        }
+
+                        // Have we seen our left variable somewhere else? Add our conjunct in our main collection.
+                        if (visitedVariables.contains(leftVariable)) {
+                            VariableExpr joinVariable1 = deepCopyVisitor.visit(joinClause.getRightVariable(), null);
+                            VariableExpr joinVariable2 = deepCopyVisitor.visit(joinClause.getRightVariable(), null);
+                            FieldAccessor fieldAccessor1 = new FieldAccessor(joinVariable1, rightVariable.getVar());
+                            FieldAccessor fieldAccessor2 = new FieldAccessor(joinVariable2, rightVariable.getVar());
+                            remapCloneVisitor.addSubstitution(rightVariable, fieldAccessor1);
+                            ILangExpression qualifiedConjunct = remapCloneVisitor.substitute(isomorphismConjunct);
+
+                            // Our right variable can also be optional.
+                            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.IS_MISSING);
+                            CallExpr isMissingCallExpr = new CallExpr(functionSignature, List.of(fieldAccessor2));
+                            OperatorExpr disjunctionExpr = new OperatorExpr();
+                            disjunctionExpr.addOperator(OperatorType.OR);
+                            disjunctionExpr.addOperand(isMissingCallExpr);
+                            disjunctionExpr.addOperand((Expression) qualifiedConjunct);
+                            clauseIterator.add(new WhereClause(disjunctionExpr));
+                            appliedIsomorphismConjuncts.add(isomorphismConjunct);
+                            visitedVariables.add(rightVariable);
+                        }
+                    }
+                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
+                }
+            }
+        });
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/PathPatternAction.java
similarity index 76%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/PathPatternAction.java
index bb9f849..6a6f849 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/PathPatternAction.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.action;
+package org.apache.asterix.graphix.lang.rewrite.lower.action;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -24,18 +24,17 @@
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrite.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.type.MaterializePathTypeComputer;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.FieldBinding;
 import org.apache.asterix.lang.common.expression.ListConstructor;
 import org.apache.asterix.lang.common.expression.LiteralExpr;
 import org.apache.asterix.lang.common.expression.RecordConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.literal.StringLiteral;
 
 /**
@@ -44,9 +43,6 @@
  * edge variables.
  */
 public class PathPatternAction implements IEnvironmentAction {
-    public final static String PATH_VERTICES_FIELD_NAME = "Vertices";
-    public final static String PATH_EDGES_FIELD_NAME = "Edges";
-
     private final PathPatternExpr pathPatternExpr;
 
     public PathPatternAction(PathPatternExpr pathPatternExpr) {
@@ -56,28 +52,27 @@
     @Override
     public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
         if (pathPatternExpr.getVariableExpr() != null) {
-            loweringEnvironment.acceptTransformer(clauseSequence -> {
+            loweringEnvironment.acceptTransformer(lowerList -> {
                 // We have a named non-sub-path.
                 RecordConstructor recordConstructor = new RecordConstructor();
+                recordConstructor.setSourceLocation(pathPatternExpr.getSourceLocation());
                 List<VertexPatternExpr> vertexExpressions = pathPatternExpr.getVertexExpressions();
                 List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
                 buildPathRecord(vertexExpressions, edgeExpressions, recordConstructor);
-                VariableExpr pathVariableExpr = new VariableExpr(pathPatternExpr.getVariableExpr().getVar());
-                clauseSequence.addDeferredClause(new CorrLetClause(recordConstructor, pathVariableExpr, null));
+                lowerList.addPathBinding(pathPatternExpr.getVariableExpr(), recordConstructor);
             });
         }
         for (LetClause reboundSubPathExpression : pathPatternExpr.getReboundSubPathList()) {
-            loweringEnvironment.acceptTransformer(clauseSequence -> {
+            loweringEnvironment.acceptTransformer(lowerList -> {
                 // We have sub-paths we need to introduce.
-                VariableExpr pathVariableExpr = new VariableExpr(reboundSubPathExpression.getVarExpr().getVar());
                 Expression reboundExpression = reboundSubPathExpression.getBindingExpr();
-                clauseSequence.addDeferredClause(new CorrLetClause(reboundExpression, pathVariableExpr, null));
+                lowerList.addPathBinding(reboundSubPathExpression.getVarExpr(), reboundExpression);
             });
         }
     }
 
     public static void buildPathRecord(Collection<VertexPatternExpr> vertexExpressions,
-            List<EdgePatternExpr> edgeExpressions, RecordConstructor outputRecordConstructor) {
+            Collection<EdgePatternExpr> edgeExpressions, RecordConstructor outputRecordConstructor) {
         List<FieldBinding> fieldBindingList = new ArrayList<>();
 
         // Assemble our vertices into a list.
@@ -86,7 +81,9 @@
         ListConstructor vertexVariableList = new ListConstructor();
         vertexVariableList.setType(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR);
         vertexVariableList.setExprList(vertexVariableExprList);
-        LiteralExpr verticesFieldName = new LiteralExpr(new StringLiteral(PATH_VERTICES_FIELD_NAME));
+        vertexVariableList.setSourceLocation(outputRecordConstructor.getSourceLocation());
+        StringLiteral verticesFieldNameLiteral = new StringLiteral(MaterializePathTypeComputer.VERTICES_FIELD_NAME);
+        LiteralExpr verticesFieldName = new LiteralExpr(verticesFieldNameLiteral);
         fieldBindingList.add(new FieldBinding(verticesFieldName, vertexVariableList));
 
         // Assemble our edges into a list.
@@ -95,7 +92,9 @@
         ListConstructor edgeVariableList = new ListConstructor();
         edgeVariableList.setType(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR);
         edgeVariableList.setExprList(edgeVariableExprList);
-        LiteralExpr edgesFieldName = new LiteralExpr(new StringLiteral(PATH_EDGES_FIELD_NAME));
+        edgeVariableList.setSourceLocation(outputRecordConstructor.getSourceLocation());
+        StringLiteral edgesFieldNameLiteral = new StringLiteral(MaterializePathTypeComputer.EDGES_FIELD_NAME);
+        LiteralExpr edgesFieldName = new LiteralExpr(edgesFieldNameLiteral);
         fieldBindingList.add(new FieldBinding(edgesFieldName, edgeVariableList));
 
         // Set our field bindings in our output record constructor.
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/ClauseCollection.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/ClauseCollection.java
new file mode 100644
index 0000000..0c10586
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/ClauseCollection.java
@@ -0,0 +1,232 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower.struct;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+public class ClauseCollection implements Iterable<AbstractClause> {
+    private final List<LetClause> representativeVertexBindings = new ArrayList<>();
+    private final List<LetClause> representativeEdgeBindings = new ArrayList<>();
+    private final List<LetClause> representativePathBindings = new ArrayList<>();
+    private final List<AbstractClause> nonRepresentativeClauses = new LinkedList<>();
+    private final List<Pair<VariableExpr, LetClause>> eagerVertexBindings = new ArrayList<>();
+    private final List<AbstractBinaryCorrelateClause> userCorrelateClauses = new ArrayList<>();
+    private final SourceLocation sourceLocation;
+
+    public ClauseCollection(SourceLocation sourceLocation) {
+        this.sourceLocation = sourceLocation;
+    }
+
+    public void addVertexBinding(VariableExpr bindingVar, Expression boundExpression) throws CompilationException {
+        if (representativeVertexBindings.stream().anyMatch(v -> v.getVarExpr().equals(bindingVar))) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Duplicate vertex binding found!");
+        }
+        representativeVertexBindings.add(new LetClause(bindingVar, boundExpression));
+    }
+
+    public void addEdgeBinding(VariableExpr bindingVar, Expression boundExpression) throws CompilationException {
+        if (representativeEdgeBindings.stream().anyMatch(v -> v.getVarExpr().equals(bindingVar))) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Duplicate edge binding found!");
+        }
+        representativeEdgeBindings.add(new LetClause(bindingVar, boundExpression));
+    }
+
+    public void addPathBinding(VariableExpr bindingVar, Expression boundExpression) throws CompilationException {
+        if (representativePathBindings.stream().anyMatch(v -> v.getVarExpr().equals(bindingVar))) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Duplicate path binding found!");
+        }
+        representativePathBindings.add(new LetClause(bindingVar, boundExpression));
+    }
+
+    @SuppressWarnings("fallthrough")
+    public void addNonRepresentativeClause(AbstractClause abstractClause) throws CompilationException {
+        switch (abstractClause.getClauseType()) {
+            case JOIN_CLAUSE:
+            case UNNEST_CLAUSE:
+            case LET_CLAUSE:
+            case WHERE_CLAUSE:
+                nonRepresentativeClauses.add(abstractClause);
+                break;
+
+            case EXTENSION:
+                if (abstractClause instanceof LowerSwitchClause) {
+                    nonRepresentativeClauses.add(abstractClause);
+                    break;
+                }
+
+            default:
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, abstractClause.getSourceLocation(),
+                        "Illegal clause inserted into the lower clause list!");
+        }
+    }
+
+    public void addEagerVertexBinding(VariableExpr representativeVariable, LetClause vertexBinding)
+            throws CompilationException {
+        // An eager vertex binding should be found in the non-representative clauses...
+        boolean isIllegalVertexBinding = true;
+        for (AbstractClause nonRepresentativeClause : nonRepresentativeClauses) {
+            if (nonRepresentativeClause.getClauseType() != Clause.ClauseType.LET_CLAUSE) {
+                continue;
+            }
+            LetClause nonRepresentativeLetClause = (LetClause) nonRepresentativeClause;
+            if (nonRepresentativeLetClause.getBindingExpr().equals(vertexBinding.getBindingExpr())) {
+                isIllegalVertexBinding = false;
+                break;
+            }
+        }
+        if (isIllegalVertexBinding) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Illegal eager vertex binding added!");
+        }
+
+        // ... and the representative vertex bindings.
+        isIllegalVertexBinding = true;
+        for (LetClause representativeVertexBinding : representativeVertexBindings) {
+            if (representativeVertexBinding.getBindingExpr().equals(vertexBinding.getBindingExpr())
+                    && representativeVariable.equals(representativeVertexBinding.getVarExpr())) {
+                isIllegalVertexBinding = false;
+                break;
+            }
+        }
+        if (isIllegalVertexBinding) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Illegal eager vertex binding added!");
+        }
+        eagerVertexBindings.add(new Pair<>(representativeVariable, vertexBinding));
+    }
+
+    public void addUserDefinedCorrelateClause(AbstractBinaryCorrelateClause correlateClause) {
+        userCorrelateClauses.add(correlateClause);
+    }
+
+    public List<LetClause> getRepresentativeVertexBindings() {
+        return representativeVertexBindings;
+    }
+
+    public List<Pair<VariableExpr, LetClause>> getEagerVertexBindings() {
+        return eagerVertexBindings;
+    }
+
+    public List<LetClause> getRepresentativeEdgeBindings() {
+        return representativeEdgeBindings;
+    }
+
+    public List<LetClause> getRepresentativePathBindings() {
+        return representativePathBindings;
+    }
+
+    public List<AbstractClause> getNonRepresentativeClauses() {
+        return nonRepresentativeClauses;
+    }
+
+    public List<AbstractBinaryCorrelateClause> getUserDefinedCorrelateClauses() {
+        return userCorrelateClauses;
+    }
+
+    public SourceLocation getSourceLocation() {
+        return sourceLocation;
+    }
+
+    @Override
+    public Iterator<AbstractClause> iterator() {
+        Iterator<AbstractClause> nonRepresentativeIterator = nonRepresentativeClauses.iterator();
+        Iterator<LetClause> representativeVertexIterator = representativeVertexBindings.iterator();
+        Iterator<LetClause> representativeEdgeIterator = representativeEdgeBindings.iterator();
+        Iterator<LetClause> representativePathIterator = representativePathBindings.iterator();
+        Iterator<AbstractBinaryCorrelateClause> userCorrelateIterator = userCorrelateClauses.iterator();
+        return new Iterator<>() {
+            @Override
+            public boolean hasNext() {
+                return nonRepresentativeIterator.hasNext() || representativeVertexIterator.hasNext()
+                        || representativeEdgeIterator.hasNext() || representativePathIterator.hasNext()
+                        || userCorrelateIterator.hasNext();
+            }
+
+            @Override
+            public AbstractClause next() {
+                if (nonRepresentativeIterator.hasNext()) {
+                    return nonRepresentativeIterator.next();
+                }
+                if (representativeVertexIterator.hasNext()) {
+                    return representativeVertexIterator.next();
+                }
+                if (representativeEdgeIterator.hasNext()) {
+                    return representativeEdgeIterator.next();
+                }
+                if (representativePathIterator.hasNext()) {
+                    return representativePathIterator.next();
+                }
+                if (userCorrelateIterator.hasNext()) {
+                    return userCorrelateIterator.next();
+                }
+                throw new IllegalStateException();
+            }
+        };
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(nonRepresentativeClauses, representativeVertexBindings, representativeEdgeBindings,
+                eagerVertexBindings, representativePathBindings, userCorrelateClauses, sourceLocation);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof ClauseCollection)) {
+            return false;
+        }
+        ClauseCollection that = (ClauseCollection) object;
+        return Objects.equals(this.nonRepresentativeClauses, that.nonRepresentativeClauses)
+                && Objects.equals(this.representativeVertexBindings, that.representativeVertexBindings)
+                && Objects.equals(this.representativeEdgeBindings, that.representativeEdgeBindings)
+                && Objects.equals(this.representativePathBindings, that.representativePathBindings)
+                && Objects.equals(this.eagerVertexBindings, that.eagerVertexBindings)
+                && Objects.equals(this.userCorrelateClauses, that.userCorrelateClauses)
+                && Objects.equals(this.sourceLocation, that.sourceLocation);
+    }
+
+    @Override
+    public String toString() {
+        final Function<List<LetClause>, String> bindingPrinter = letClauses -> letClauses.stream()
+                .map(b -> b.getVarExpr().getVar().toString()).collect(Collectors.joining(","));
+        return String.format("clause-collection:\n\t%s\n\t%s\n\t%s\n",
+                "vertices: " + bindingPrinter.apply(representativeVertexBindings),
+                "edges: " + bindingPrinter.apply(representativeEdgeBindings),
+                "paths: " + bindingPrinter.apply(representativePathBindings));
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/CollectionTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/CollectionTable.java
new file mode 100644
index 0000000..29b1fa4
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/CollectionTable.java
@@ -0,0 +1,174 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower.struct;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+public class CollectionTable implements Iterable<ClauseCollection> {
+    private final Map<ElementLabel, List<Entry>> collectionMap = new HashMap<>();
+    private Map<ElementLabel, StateContainer> inputMap;
+    private Map<ElementLabel, StateContainer> outputMap;
+
+    public static class Entry {
+        private final ElementLabel edgeLabel;
+        private final ElementLabel destinationLabel;
+        private final ClauseCollection clauseCollection;
+        private final EdgeDescriptor.EdgeDirection edgeDirection;
+
+        private Entry(ElementLabel edgeLabel, ElementLabel destinationLabel, ClauseCollection clauseCollection,
+                EdgeDescriptor.EdgeDirection edgeDirection) {
+            this.edgeLabel = edgeLabel;
+            this.destinationLabel = destinationLabel;
+            this.clauseCollection = clauseCollection;
+            this.edgeDirection = edgeDirection;
+        }
+
+        public ElementLabel getEdgeLabel() {
+            return edgeLabel;
+        }
+
+        public ElementLabel getDestinationLabel() {
+            return destinationLabel;
+        }
+
+        public ClauseCollection getClauseCollection() {
+            return clauseCollection;
+        }
+
+        public EdgeDescriptor.EdgeDirection getEdgeDirection() {
+            return edgeDirection;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(edgeLabel, destinationLabel, clauseCollection, edgeDirection);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (!(object instanceof Entry)) {
+                return false;
+            }
+            Entry that = (Entry) object;
+            return Objects.equals(this.edgeLabel, that.edgeLabel)
+                    && Objects.equals(this.destinationLabel, that.destinationLabel)
+                    && Objects.equals(this.clauseCollection, that.clauseCollection)
+                    && Objects.equals(this.edgeDirection, that.edgeDirection);
+        }
+    }
+
+    public void setInputMap(Map<ElementLabel, StateContainer> inputMap) {
+        this.inputMap = inputMap;
+    }
+
+    public void setOutputMap(Map<ElementLabel, StateContainer> outputMap) {
+        this.outputMap = outputMap;
+    }
+
+    public void putCollection(EdgePatternExpr edgePatternExpr, boolean isEvaluatingFromLeft,
+            ClauseCollection clauseCollection) {
+        // Determine our source and destination labels.
+        ElementLabel startLabel, edgeLabel, endLabel;
+        if (isEvaluatingFromLeft) {
+            startLabel = edgePatternExpr.getLeftVertex().getLabels().iterator().next();
+            endLabel = edgePatternExpr.getRightVertex().getLabels().iterator().next();
+
+        } else {
+            startLabel = edgePatternExpr.getRightVertex().getLabels().iterator().next();
+            endLabel = edgePatternExpr.getLeftVertex().getLabels().iterator().next();
+        }
+        edgeLabel = edgePatternExpr.getEdgeDescriptor().getEdgeLabels().iterator().next();
+
+        // Insert our collection.
+        EdgeDescriptor.EdgeDirection edgeDirection = edgePatternExpr.getEdgeDescriptor().getEdgeDirection();
+        putCollection(startLabel, edgeLabel, endLabel, clauseCollection, edgeDirection);
+    }
+
+    public void putCollection(ElementLabel startLabel, ElementLabel edgeLabel, ElementLabel endLabel,
+            ClauseCollection clauseCollection, EdgeDescriptor.EdgeDirection edgeDirection) {
+        if (!collectionMap.containsKey(startLabel)) {
+            collectionMap.put(startLabel, new ArrayList<>());
+        }
+        collectionMap.get(startLabel).add(new Entry(edgeLabel, endLabel, clauseCollection, edgeDirection));
+    }
+
+    public Map<ElementLabel, StateContainer> getInputMap() {
+        return inputMap;
+    }
+
+    public Map<ElementLabel, StateContainer> getOutputMap() {
+        return outputMap;
+    }
+
+    public Iterator<Pair<ElementLabel, List<Entry>>> entryIterator() {
+        return collectionMap.entrySet().stream().map(e -> new Pair<>(e.getKey(), e.getValue())).iterator();
+    }
+
+    @Override
+    public Iterator<ClauseCollection> iterator() {
+        return collectionMap.values().stream().flatMap(e -> e.stream().map(c -> c.clauseCollection)).iterator();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(collectionMap, inputMap, outputMap);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof CollectionTable)) {
+            return false;
+        }
+        CollectionTable that = (CollectionTable) object;
+        return Objects.equals(this.collectionMap, that.collectionMap) && Objects.equals(this.inputMap, that.inputMap)
+                && Objects.equals(this.outputMap, that.outputMap);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("collection-table: \n");
+        Iterator<Pair<ElementLabel, List<Entry>>> entryIterator = this.entryIterator();
+        while (entryIterator.hasNext()) {
+            Pair<ElementLabel, List<Entry>> mapEntry = entryIterator.next();
+            for (Entry entry : mapEntry.second) {
+                sb.append("\t").append(mapEntry.getFirst()).append(" to ");
+                sb.append(entry.getDestinationLabel()).append(" [ ");
+                sb.append(entry.getEdgeLabel()).append(" ]\n");
+            }
+        }
+        return sb.toString();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/StateContainer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/StateContainer.java
new file mode 100644
index 0000000..379bf78
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/struct/StateContainer.java
@@ -0,0 +1,66 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.lower.struct;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+public class StateContainer implements Iterable<VariableExpr> {
+    private final VariableExpr iterationVariable;
+    private final VariableExpr joinVariable;
+
+    public StateContainer(VariableExpr iterationVariable, VariableExpr joinVariable) {
+        this.iterationVariable = iterationVariable;
+        this.joinVariable = joinVariable;
+    }
+
+    public VariableExpr getIterationVariable() {
+        return iterationVariable;
+    }
+
+    public VariableExpr getJoinVariable() {
+        return joinVariable;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(iterationVariable, joinVariable);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof StateContainer)) {
+            return false;
+        }
+        StateContainer that = (StateContainer) object;
+        return Objects.equals(this.iterationVariable, that.iterationVariable)
+                && Objects.equals(this.joinVariable, that.joinVariable);
+    }
+
+    @Override
+    public Iterator<VariableExpr> iterator() {
+        return List.of(iterationVariable, joinVariable).iterator();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitor.java
similarity index 85%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitor.java
index fba1762..2f70c01 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.lang.rewrite.print;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -24,21 +24,19 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.visitor.SqlppAstPrintVisitor;
 
 public class GraphixASTPrintVisitor extends SqlppAstPrintVisitor implements IGraphixLangVisitor<Void, Integer> {
@@ -80,34 +78,9 @@
     }
 
     @Override
-    public Void visit(SelectBlock selectBlock, Integer step) throws CompilationException {
-        return (selectBlock instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) selectBlock, step)
-                : super.visit(selectBlock, step);
-    }
-
-    @Override
-    public Void visit(GraphSelectBlock graphSelectBlock, Integer step) throws CompilationException {
-        graphSelectBlock.getSelectClause().accept(this, step);
-        if (graphSelectBlock.hasFromClause()) {
-            graphSelectBlock.getFromClause().accept(this, step);
-
-        } else if (graphSelectBlock.hasFromGraphClause()) {
-            graphSelectBlock.getFromGraphClause().accept(this, step);
-        }
-        if (graphSelectBlock.hasLetWhereClauses()) {
-            for (AbstractClause letWhereClause : graphSelectBlock.getLetWhereList()) {
-                letWhereClause.accept(this, step);
-            }
-        }
-        if (graphSelectBlock.hasGroupbyClause()) {
-            graphSelectBlock.getGroupbyClause().accept(this, step);
-            if (graphSelectBlock.hasLetHavingClausesAfterGroupby()) {
-                for (AbstractClause letHavingClause : graphSelectBlock.getLetHavingListAfterGroupby()) {
-                    letHavingClause.accept(this, step);
-                }
-            }
-        }
-        return null;
+    public Void visit(FromClause fromClause, Integer step) throws CompilationException {
+        return (fromClause instanceof FromGraphClause) ? this.visit((FromGraphClause) fromClause, step)
+                : super.visit(fromClause, step);
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitorFactory.java
similarity index 95%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitorFactory.java
index ff00077..f3955e6 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/GraphixASTPrintVisitorFactory.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.lang.rewrite.print;
 
 import java.io.PrintWriter;
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/SqlppASTPrintQueryVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/SqlppASTPrintQueryVisitor.java
similarity index 84%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/SqlppASTPrintQueryVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/SqlppASTPrintQueryVisitor.java
index 0a1b942..33ef30e 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/SqlppASTPrintQueryVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/print/SqlppASTPrintQueryVisitor.java
@@ -14,20 +14,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.lang.rewrite.print;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Spliterator;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
+import java.util.stream.StreamSupport;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.CollectionTable;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Clause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.base.IVisitorExtension;
 import org.apache.asterix.lang.common.clause.GroupbyClause;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.clause.LimitClause;
@@ -35,7 +45,6 @@
 import org.apache.asterix.lang.common.clause.WhereClause;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
 import org.apache.asterix.lang.common.expression.IfExpr;
 import org.apache.asterix.lang.common.expression.IndexAccessor;
 import org.apache.asterix.lang.common.expression.ListConstructor;
@@ -47,6 +56,7 @@
 import org.apache.asterix.lang.common.expression.UnaryExpr;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.statement.Query;
+import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.OperatorType;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.lang.sqlpp.clause.FromClause;
@@ -69,16 +79,11 @@
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppQueryExpressionVisitor;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.hyracks.algebricks.common.exceptions.NotImplementedException;
+import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
- * Visitor class to create a _valid_ SQL++ query out of a **valid** AST. The only exception to the validity of the
- * printed SQL++ is the presence of {@link CorrLetClause} and {@link CorrWhereClause}, denoted using "WITH ..." before
- * (or after) JOIN / UNNEST clauses. We make the following assumptions:
- * 1. The entry point is a {@link Query}, which we will print to.
- * 2. All {@link GroupbyClause} nodes only have one set of {@link GbyVariableExpressionPair} (i.e. the GROUP-BY
- * rewrites should have fired).
- * 3. No {@link WindowExpression} nodes are included (this is on the TODO list).
+ * Visitor class to create a somewhat-valid SQL++ query out of a <b>valid</b> AST.
  */
 public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisitor<String, Void> {
     private final PrintWriter printWriter;
@@ -194,8 +199,58 @@
 
     @Override
     public String visit(FromClause fromClause, Void arg) {
-        return String.format(" FROM %s ", fromClause.getFromTerms().stream().map(this::visitAndSwallowException)
-                .collect(Collectors.joining(", ")));
+        final Function<AbstractClause, String> abstractClauseFormatter = c -> {
+            if (c.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                return " LET " + visitAndSwallowException(c);
+
+            } else if (c.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                return " WHERE " + visitAndSwallowException(c);
+            }
+            return visitAndSwallowException(c);
+        };
+        final Function<LowerSwitchClause, String> fixedPointClauseFormatter = c -> {
+            LowerSwitchClause.ClauseOutputEnvironment outputEnvironment = c.getClauseOutputEnvironment();
+            String outputString =
+                    String.format("`%s` = { \"vertex-iteration\": `%s`, \"vertex-join\": `%s`, \"path\": `%s` }",
+                            formatIdentifierForQuery(outputEnvironment.getOutputVariable().getVar()),
+                            formatIdentifierForQuery(outputEnvironment.getOutputVertexIterationVariable().getVar()),
+                            formatIdentifierForQuery(outputEnvironment.getOutputVertexJoinVariable().getVar()),
+                            formatIdentifierForQuery(outputEnvironment.getPathVariable().getVar()));
+            Iterator<Pair<ElementLabel, List<CollectionTable.Entry>>> entryIterator =
+                    c.getCollectionLookupTable().entryIterator();
+            List<String> collectionKeyValuePairStrings = new ArrayList<>();
+            while (entryIterator.hasNext()) {
+                Pair<ElementLabel, List<CollectionTable.Entry>> mapEntry = entryIterator.next();
+                for (CollectionTable.Entry entry : mapEntry.second) {
+                    collectionKeyValuePairStrings
+                            .add(String.format("\"%s TO %s [ %s ] TO %s\":  [ %s ] ", mapEntry.getFirst(),
+                                    entry.getEdgeLabel(), entry.getEdgeDirection(), entry.getDestinationLabel(),
+                                    StreamSupport.stream(entry.getClauseCollection().spliterator(), false)
+                                            .map(abstractClauseFormatter).collect(Collectors.joining(", "))));
+                }
+            }
+            ElementLabel startingLabel = c.getClauseInputEnvironment().getStartingLabel();
+            return String.format("%s = { \"StartLabel\": %s, %s }", outputString, startingLabel,
+                    String.join(",", collectionKeyValuePairStrings));
+        };
+
+        if (fromClause instanceof FromGraphClause) {
+            FromGraphClause fromGraphClause = (FromGraphClause) fromClause;
+            LowerListClause lowerClause = (LowerListClause) fromGraphClause.getLowerClause();
+            Spliterator<AbstractClause> clauseCollectionIterator = lowerClause.getClauseCollection().spliterator();
+            return String.format(" FROM %s ", StreamSupport.stream(clauseCollectionIterator, false).map(c -> {
+                if (c instanceof LowerSwitchClause) {
+                    return " FIXED-POINT " + fixedPointClauseFormatter.apply((LowerSwitchClause) c);
+
+                } else {
+                    return abstractClauseFormatter.apply(c);
+                }
+            }).collect(Collectors.joining(", ")));
+
+        } else {
+            return String.format(" FROM %s ", fromClause.getFromTerms().stream().map(this::visitAndSwallowException)
+                    .collect(Collectors.joining(", ")));
+        }
     }
 
     @Override
@@ -211,9 +266,6 @@
             sb.append(fromTerm.getPositionalVariable().accept(this, arg));
         }
         for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
-            if (correlateClause instanceof CorrLetClause || correlateClause instanceof CorrWhereClause) {
-                sb.append(" WITH ");
-            }
             sb.append(correlateClause.accept(this, arg));
         }
         return sb.toString();
@@ -238,11 +290,9 @@
             sb.append(groupbyClause.getGroupVar().accept(this, arg));
             if (groupbyClause.hasGroupFieldList()) {
                 sb.append(" ( ");
-                sb.append(
-                        groupbyClause.getGroupFieldList().stream()
-                                .map(f -> visitAndSwallowException(f.first) + " AS "
-                                        + formatIdentifierForQuery(f.second.getValue()))
-                                .collect(Collectors.joining(", ")));
+                sb.append(groupbyClause.getGroupFieldList().stream()
+                        .map(f -> visitAndSwallowException(f.first) + " AS " + formatIdentifierForQuery(f.second))
+                        .collect(Collectors.joining(", ")));
                 sb.append(" ) ");
             }
         }
@@ -441,7 +491,7 @@
 
     @Override
     public String visit(VariableExpr variableExpr, Void arg) {
-        return "`" + formatIdentifierForQuery(variableExpr.getVar().getValue()) + "`";
+        return "`" + formatIdentifierForQuery(variableExpr.getVar()) + "`";
     }
 
     @Override
@@ -560,8 +610,8 @@
 
     @Override
     public String visit(FieldAccessor fieldAccessor, Void arg) throws CompilationException {
-        return fieldAccessor.getExpr().accept(this, arg) + ".`"
-                + formatIdentifierForQuery(fieldAccessor.getIdent().getValue()) + "`";
+        return fieldAccessor.getExpr().accept(this, arg) + ".`" + formatIdentifierForQuery(fieldAccessor.getIdent())
+                + "`";
     }
 
     @Override
@@ -595,6 +645,12 @@
     }
 
     @Override
+    public String visit(IVisitorExtension extensionClause, Void arg) throws CompilationException {
+        // TODO (GLENN): Push this class to AsterixDB, and create a hook.
+        return null;
+    }
+
+    @Override
     public String visit(UnaryExpr unaryExpr, Void arg) throws CompilationException {
         StringBuilder sb = new StringBuilder();
         if (unaryExpr.getExprType() != null) {
@@ -665,7 +721,7 @@
 
     @Override
     public String visit(WindowExpression windowExpression, Void arg) {
-        // TODO: Add support for WINDOW-EXPR here.
+        // TODO (GLENN): Add support for WINDOW-EXPR here.
         throw new NotImplementedException("WindowExpression not currently supported!");
     }
 
@@ -693,4 +749,8 @@
             return identifier;
         }
     }
+
+    private String formatIdentifierForQuery(Identifier identifier) {
+        return formatIdentifierForQuery(identifier.getValue());
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/ExhaustiveSearchResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/ExhaustiveSearchResolver.java
new file mode 100644
index 0000000..4c52a53
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/ExhaustiveSearchResolver.java
@@ -0,0 +1,502 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.resolve;
+
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.expandElementLabels;
+import static org.apache.asterix.graphix.lang.rewrite.util.CanonicalElementUtil.expandFixedPathPattern;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.annotation.SubqueryVertexJoinAnnotation;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor.EdgeDirection;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.struct.PatternGroup;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+public class ExhaustiveSearchResolver implements IPatternGroupResolver {
+    private final GraphixDeepCopyVisitor deepCopyVisitor;
+    private final SchemaKnowledgeTable schemaKnowledgeTable;
+
+    // Vertex / edge expression map to a list of canonical vertex / edge expressions.
+    private final Map<VariableExpr, List<AbstractExpression>> canonicalExpressionsMap;
+    private final Map<VariableExpr, AbstractExpression> originalExpressionMap;
+
+    public ExhaustiveSearchResolver(SchemaKnowledgeTable schemaKnowledgeTable) {
+        this.schemaKnowledgeTable = schemaKnowledgeTable;
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.canonicalExpressionsMap = new LinkedHashMap<>();
+        this.originalExpressionMap = new LinkedHashMap<>();
+    }
+
+    private void expandVertexPattern(VertexPatternExpr unexpandedVertexPattern, Deque<PatternGroup> inputPatternGroups,
+            Deque<PatternGroup> outputPatternGroups) throws CompilationException {
+        VariableExpr vertexVariable = unexpandedVertexPattern.getVariableExpr();
+        Set<ElementLabel> vertexLabelSet = unexpandedVertexPattern.getLabels();
+        Set<ElementLabel> expandedLabelSet =
+                expandElementLabels(unexpandedVertexPattern, vertexLabelSet, schemaKnowledgeTable);
+        while (!inputPatternGroups.isEmpty()) {
+            PatternGroup unexpandedPatternGroup = inputPatternGroups.pop();
+            for (ElementLabel vertexLabel : expandedLabelSet) {
+                PatternGroup expandedPatternGroup = new PatternGroup();
+                for (VertexPatternExpr vertexPatternExpr : unexpandedPatternGroup.getVertexPatternSet()) {
+                    VertexPatternExpr clonedVertexPattern = deepCopyVisitor.visit(vertexPatternExpr, null);
+                    SubqueryVertexJoinAnnotation hint = vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                    if (vertexPatternExpr.getVariableExpr().equals(vertexVariable)
+                            || (hint != null && hint.getSourceVertexVariable().equals(vertexVariable))) {
+                        clonedVertexPattern.getLabels().clear();
+                        clonedVertexPattern.getLabels().add(vertexLabel);
+                    }
+                    expandedPatternGroup.getVertexPatternSet().add(clonedVertexPattern);
+                }
+                for (EdgePatternExpr unexpandedEdgePattern : unexpandedPatternGroup.getEdgePatternSet()) {
+                    EdgePatternExpr clonedEdgePattern = deepCopyVisitor.visit(unexpandedEdgePattern, null);
+                    VertexPatternExpr clonedLeftVertex = clonedEdgePattern.getLeftVertex();
+                    SubqueryVertexJoinAnnotation hint1 = clonedLeftVertex.findHint(SubqueryVertexJoinAnnotation.class);
+                    if (clonedLeftVertex.getVariableExpr().equals(vertexVariable)
+                            || (hint1 != null && hint1.getSourceVertexVariable().equals(vertexVariable))) {
+                        clonedLeftVertex.getLabels().clear();
+                        clonedLeftVertex.getLabels().add(vertexLabel);
+                    }
+
+                    VertexPatternExpr clonedRightVertex = clonedEdgePattern.getRightVertex();
+                    SubqueryVertexJoinAnnotation hint2 = clonedLeftVertex.findHint(SubqueryVertexJoinAnnotation.class);
+                    if (clonedRightVertex.getVariableExpr().equals(vertexVariable)
+                            || (hint2 != null && hint2.getSourceVertexVariable().equals(vertexVariable))) {
+                        clonedRightVertex.getLabels().clear();
+                        clonedRightVertex.getLabels().add(vertexLabel);
+                    }
+                    expandedPatternGroup.getEdgePatternSet().add(clonedEdgePattern);
+                }
+                unifyVertexLabels(expandedPatternGroup);
+                outputPatternGroups.push(expandedPatternGroup);
+            }
+        }
+    }
+
+    private void expandEdgePattern(EdgePatternExpr unexpandedEdgePattern, Deque<PatternGroup> inputPatternGroups,
+            Deque<PatternGroup> outputPatternGroups) throws CompilationException {
+        EdgeDescriptor unexpandedEdgeDescriptor = unexpandedEdgePattern.getEdgeDescriptor();
+        VariableExpr edgeVariable = unexpandedEdgeDescriptor.getVariableExpr();
+        Set<ElementLabel> edgeLabelSet = unexpandedEdgeDescriptor.getEdgeLabels();
+        Set<ElementLabel> expandedLabelSet =
+                expandElementLabels(unexpandedEdgePattern, edgeLabelSet, schemaKnowledgeTable);
+
+        // Determine the direction of our edge.
+        Set<EdgeDirection> edgeDirectionSet = new HashSet<>();
+        if (unexpandedEdgeDescriptor.getEdgeDirection() != EdgeDirection.RIGHT_TO_LEFT) {
+            edgeDirectionSet.add(EdgeDirection.LEFT_TO_RIGHT);
+        }
+        if (unexpandedEdgeDescriptor.getEdgeDirection() != EdgeDirection.LEFT_TO_RIGHT) {
+            edgeDirectionSet.add(EdgeDirection.RIGHT_TO_LEFT);
+        }
+
+        // Expand an edge pattern.
+        while (!inputPatternGroups.isEmpty()) {
+            PatternGroup unexpandedPatternGroup = inputPatternGroups.pop();
+            for (ElementLabel edgeLabel : expandedLabelSet) {
+                for (EdgeDirection edgeDirection : edgeDirectionSet) {
+                    PatternGroup expandedPatternGroup = new PatternGroup();
+                    expandedPatternGroup.getVertexPatternSet().addAll(unexpandedPatternGroup.getVertexPatternSet());
+                    for (EdgePatternExpr edgePatternExpr : unexpandedPatternGroup.getEdgePatternSet()) {
+                        // Update our edge descriptor, if necessary.
+                        EdgePatternExpr clonedEdgePattern = deepCopyVisitor.visit(edgePatternExpr, null);
+                        if (edgeVariable.equals(edgePatternExpr.getEdgeDescriptor().getVariableExpr())) {
+                            clonedEdgePattern.getEdgeDescriptor().getEdgeLabels().clear();
+                            clonedEdgePattern.getEdgeDescriptor().getEdgeLabels().add(edgeLabel);
+                            clonedEdgePattern.getEdgeDescriptor().setEdgeDirection(edgeDirection);
+                        }
+                        expandedPatternGroup.getEdgePatternSet().add(clonedEdgePattern);
+                    }
+                    unifyVertexLabels(expandedPatternGroup);
+                    outputPatternGroups.push(expandedPatternGroup);
+                }
+            }
+        }
+    }
+
+    private void expandPathPattern(EdgePatternExpr unexpandedPathPattern, Deque<PatternGroup> inputPatternGroups,
+            Deque<PatternGroup> outputPatternGroups) throws CompilationException {
+        EdgeDescriptor unexpandedEdgeDescriptor = unexpandedPathPattern.getEdgeDescriptor();
+        VariableExpr edgeVariable = unexpandedEdgeDescriptor.getVariableExpr();
+        Set<ElementLabel> edgeLabelSet = unexpandedEdgeDescriptor.getEdgeLabels();
+        Set<ElementLabel> expandedEdgeLabelSet =
+                expandElementLabels(unexpandedPathPattern, edgeLabelSet, schemaKnowledgeTable);
+
+        // Determine our candidates for internal vertex labels.
+        Set<ElementLabel> vertexLabelSet = schemaKnowledgeTable.getVertexLabels();
+
+        // Determine the direction of our edges.
+        Set<EdgeDirection> edgeDirectionSet = new HashSet<>();
+        if (unexpandedEdgeDescriptor.getEdgeDirection() != EdgeDirection.RIGHT_TO_LEFT) {
+            edgeDirectionSet.add(EdgeDirection.LEFT_TO_RIGHT);
+        }
+        if (unexpandedEdgeDescriptor.getEdgeDirection() != EdgeDirection.LEFT_TO_RIGHT) {
+            edgeDirectionSet.add(EdgeDirection.RIGHT_TO_LEFT);
+        }
+
+        // Determine the length of **expanded** path. For {N,M} w/ E labels... MIN(M, (1 + 2(E-1))).
+        int minimumExpansionLength, expansionLength;
+        Integer minimumHops = unexpandedEdgeDescriptor.getMinimumHops();
+        Integer maximumHops = unexpandedEdgeDescriptor.getMaximumHops();
+        if (Objects.equals(minimumHops, maximumHops) && minimumHops != null && minimumHops == 1) {
+            // Special case: we have a path disguised as an edge.
+            minimumExpansionLength = 1;
+            expansionLength = 1;
+
+        } else {
+            int maximumExpansionLength = 1 + 2 * (expandedEdgeLabelSet.size() - 1);
+            minimumExpansionLength = Objects.requireNonNullElse(minimumHops, 1);
+            expansionLength = Objects.requireNonNullElse(maximumHops, maximumExpansionLength);
+            if (minimumExpansionLength > expansionLength) {
+                minimumExpansionLength = expansionLength;
+            }
+        }
+
+        // Generate all possible paths, from minimumExpansionLength to expansionLength.
+        List<List<EdgePatternExpr>> candidatePaths = new ArrayList<>();
+        for (int pathLength = minimumExpansionLength; pathLength <= expansionLength; pathLength++) {
+            List<List<EdgePatternExpr>> expandedPathPattern = expandFixedPathPattern(pathLength, unexpandedPathPattern,
+                    expandedEdgeLabelSet, vertexLabelSet, edgeDirectionSet, deepCopyVisitor,
+                    () -> deepCopyVisitor.visit(unexpandedPathPattern.getInternalVertex(), null));
+            candidatePaths.addAll(expandedPathPattern);
+        }
+
+        // Push the labels of our pattern group to our edge.
+        final BiConsumer<PatternGroup, EdgePatternExpr> vertexLabelUpdater = (p, e) -> {
+            VariableExpr leftVariable = e.getLeftVertex().getVariableExpr();
+            VariableExpr rightVariable = e.getRightVertex().getVariableExpr();
+            p.getVertexPatternSet().forEach(v -> {
+                VariableExpr vertexVariable = v.getVariableExpr();
+                if (leftVariable.equals(vertexVariable)) {
+                    e.getLeftVertex().getLabels().clear();
+                    e.getLeftVertex().getLabels().addAll(v.getLabels());
+                }
+                if (rightVariable.equals(vertexVariable)) {
+                    e.getRightVertex().getLabels().clear();
+                    e.getRightVertex().getLabels().addAll(v.getLabels());
+                }
+            });
+        };
+
+        // Push our expansion to our pattern groups.
+        while (!inputPatternGroups.isEmpty()) {
+            PatternGroup unexpandedPatternGroup = inputPatternGroups.pop();
+            for (List<EdgePatternExpr> candidatePath : candidatePaths) {
+                PatternGroup expandedPatternGroup = new PatternGroup();
+                expandedPatternGroup.getVertexPatternSet().addAll(unexpandedPatternGroup.getVertexPatternSet());
+                for (EdgePatternExpr edgePatternExpr : unexpandedPatternGroup.getEdgePatternSet()) {
+                    if (edgePatternExpr.getEdgeDescriptor().getVariableExpr().equals(edgeVariable)) {
+                        for (EdgePatternExpr candidateEdge : candidatePath) {
+                            EdgePatternExpr copyEdgePattern = deepCopyVisitor.visit(candidateEdge, null);
+                            vertexLabelUpdater.accept(expandedPatternGroup, copyEdgePattern);
+                            expandedPatternGroup.getEdgePatternSet().add(copyEdgePattern);
+                        }
+
+                    } else {
+                        EdgePatternExpr copyEdgePattern = deepCopyVisitor.visit(edgePatternExpr, null);
+                        vertexLabelUpdater.accept(expandedPatternGroup, copyEdgePattern);
+                        expandedPatternGroup.getEdgePatternSet().add(copyEdgePattern);
+                    }
+                }
+                outputPatternGroups.push(expandedPatternGroup);
+            }
+        }
+    }
+
+    private List<PatternGroup> expandPatternGroups(PatternGroup patternGroup) throws CompilationException {
+        // First pass: unify our vertex labels.
+        unifyVertexLabels(patternGroup);
+
+        // Second pass: collect all ambiguous graph elements.
+        Set<AbstractExpression> ambiguousExpressionSet = new LinkedHashSet<>();
+        for (AbstractExpression originalExpr : patternGroup) {
+            if (originalExpr instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) originalExpr;
+                if (vertexPatternExpr.getLabels().size() != 1
+                        || vertexPatternExpr.getLabels().iterator().next().isNegated()) {
+                    ambiguousExpressionSet.add(vertexPatternExpr);
+                }
+
+            } else { // originalExpr instanceof EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) originalExpr;
+                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                if (edgeDescriptor.getEdgeLabels().size() != 1
+                        || edgeDescriptor.getEdgeLabels().iterator().next().isNegated()
+                        || edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
+                        || edgeDescriptor.getEdgeDirection() == EdgeDirection.UNDIRECTED) {
+                    ambiguousExpressionSet.add(edgePatternExpr);
+                }
+            }
+        }
+        if (ambiguousExpressionSet.isEmpty()) {
+            // Our list must always be mutable.
+            return new ArrayList<>(List.of(patternGroup));
+        }
+
+        // Third pass: expand the ambiguous expressions.
+        Deque<PatternGroup> redPatternGroups = new ArrayDeque<>();
+        Deque<PatternGroup> blackPatternGroups = new ArrayDeque<>();
+        redPatternGroups.add(patternGroup);
+        for (AbstractExpression ambiguousExpression : ambiguousExpressionSet) {
+            // Determine our read and write pattern groups.
+            Deque<PatternGroup> readPatternGroups, writePatternGroups;
+            if (redPatternGroups.isEmpty()) {
+                readPatternGroups = blackPatternGroups;
+                writePatternGroups = redPatternGroups;
+
+            } else {
+                readPatternGroups = redPatternGroups;
+                writePatternGroups = blackPatternGroups;
+            }
+
+            // Expand our vertex / edge patterns.
+            if (ambiguousExpression instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) ambiguousExpression;
+                expandVertexPattern(vertexPatternExpr, readPatternGroups, writePatternGroups);
+
+            } else { // ambiguousExpression instance EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) ambiguousExpression;
+                if (edgePatternExpr.getEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.EDGE) {
+                    expandEdgePattern(edgePatternExpr, readPatternGroups, writePatternGroups);
+
+                } else { // edgePatternExpr.getEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.PATH
+                    expandPathPattern(edgePatternExpr, readPatternGroups, writePatternGroups);
+                }
+            }
+        }
+        return new ArrayList<>(redPatternGroups.isEmpty() ? blackPatternGroups : redPatternGroups);
+    }
+
+    private List<PatternGroup> pruneInvalidPatternGroups(List<PatternGroup> sourceGroups) {
+        ListIterator<PatternGroup> sourceGroupIterator = sourceGroups.listIterator();
+        while (sourceGroupIterator.hasNext()) {
+            PatternGroup workingPatternGroup = sourceGroupIterator.next();
+            for (AbstractExpression abstractExpression : workingPatternGroup) {
+                if (!(abstractExpression instanceof EdgePatternExpr)) {
+                    continue;
+                }
+
+                // Prune any groups with bad edges.
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) abstractExpression;
+                if (!schemaKnowledgeTable.isValidEdge(edgePatternExpr)) {
+                    sourceGroupIterator.remove();
+                    break;
+                }
+            }
+        }
+        return sourceGroups;
+    }
+
+    private void unifyVertexLabels(PatternGroup patternGroup) {
+        // Pass #1: Collect all vertex variables and their labels.
+        Map<VariableExpr, Set<ElementLabel>> vertexVariableMap = new HashMap<>();
+        for (AbstractExpression abstractExpression : patternGroup) {
+            if (abstractExpression instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) abstractExpression;
+                VariableExpr vertexVariable = vertexPatternExpr.getVariableExpr();
+                vertexVariableMap.putIfAbsent(vertexVariable, new HashSet<>());
+                vertexVariableMap.get(vertexVariable).addAll(vertexPatternExpr.getLabels());
+                SubqueryVertexJoinAnnotation hint = vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                if (hint != null) {
+                    VariableExpr sourceVertexVariable = hint.getSourceVertexVariable();
+                    vertexVariableMap.putIfAbsent(sourceVertexVariable, new HashSet<>());
+                    vertexVariableMap.get(sourceVertexVariable).addAll(vertexPatternExpr.getLabels());
+                }
+
+            } else { // abstractExpression instanceof EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) abstractExpression;
+                VertexPatternExpr leftVertexExpr = edgePatternExpr.getLeftVertex();
+                VertexPatternExpr rightVertexExpr = edgePatternExpr.getRightVertex();
+                VariableExpr leftVariable = leftVertexExpr.getVariableExpr();
+                VariableExpr rightVariable = rightVertexExpr.getVariableExpr();
+
+                // Visit the vertices of our edges and their labels as well.
+                vertexVariableMap.putIfAbsent(leftVariable, new HashSet<>());
+                vertexVariableMap.putIfAbsent(rightVariable, new HashSet<>());
+                vertexVariableMap.get(leftVariable).addAll(leftVertexExpr.getLabels());
+                vertexVariableMap.get(rightVariable).addAll(rightVertexExpr.getLabels());
+                SubqueryVertexJoinAnnotation leftHint = leftVertexExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                SubqueryVertexJoinAnnotation rightHint = rightVertexExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                if (leftHint != null) {
+                    VariableExpr sourceVertexVariable = leftHint.getSourceVertexVariable();
+                    vertexVariableMap.putIfAbsent(sourceVertexVariable, new HashSet<>());
+                    vertexVariableMap.get(sourceVertexVariable).addAll(leftVertexExpr.getLabels());
+                }
+                if (rightHint != null) {
+                    VariableExpr sourceVertexVariable = rightHint.getSourceVertexVariable();
+                    vertexVariableMap.putIfAbsent(sourceVertexVariable, new HashSet<>());
+                    vertexVariableMap.get(sourceVertexVariable).addAll(rightVertexExpr.getLabels());
+                }
+            }
+        }
+
+        // Pass #2: Copy the vertex label sets to all vertices of the same variable.
+        for (AbstractExpression abstractExpression : patternGroup) {
+            if (abstractExpression instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) abstractExpression;
+                VariableExpr vertexVariable = vertexPatternExpr.getVariableExpr();
+                vertexPatternExpr.getLabels().addAll(vertexVariableMap.get(vertexVariable));
+                SubqueryVertexJoinAnnotation hint = vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                if (hint != null) {
+                    vertexPatternExpr.getLabels().addAll(vertexVariableMap.get(hint.getSourceVertexVariable()));
+                }
+
+            } else { // abstractExpression instanceof EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) abstractExpression;
+                VertexPatternExpr leftVertexExpr = edgePatternExpr.getLeftVertex();
+                VertexPatternExpr rightVertexExpr = edgePatternExpr.getRightVertex();
+
+                // Visit the vertices of our edge as well.
+                VariableExpr leftVariable = leftVertexExpr.getVariableExpr();
+                VariableExpr rightVariable = rightVertexExpr.getVariableExpr();
+                leftVertexExpr.getLabels().addAll(vertexVariableMap.get(leftVariable));
+                rightVertexExpr.getLabels().addAll(vertexVariableMap.get(rightVariable));
+                SubqueryVertexJoinAnnotation leftHint = leftVertexExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                SubqueryVertexJoinAnnotation rightHint = rightVertexExpr.findHint(SubqueryVertexJoinAnnotation.class);
+                if (leftHint != null) {
+                    leftVertexExpr.getLabels().addAll(vertexVariableMap.get(leftHint.getSourceVertexVariable()));
+                }
+                if (rightHint != null) {
+                    rightVertexExpr.getLabels().addAll(vertexVariableMap.get(rightHint.getSourceVertexVariable()));
+                }
+            }
+        }
+
+    }
+
+    @Override
+    public void resolve(PatternGroup patternGroup) throws CompilationException {
+        // Populate our vertex / edge expression map.
+        for (VertexPatternExpr vertexPatternExpr : patternGroup.getVertexPatternSet()) {
+            VariableExpr vertexVariable = vertexPatternExpr.getVariableExpr();
+            canonicalExpressionsMap.putIfAbsent(vertexVariable, new ArrayList<>());
+            originalExpressionMap.put(vertexVariable, vertexPatternExpr);
+        }
+        for (EdgePatternExpr edgePatternExpr : patternGroup.getEdgePatternSet()) {
+            EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+            VariableExpr edgeVariable = edgeDescriptor.getVariableExpr();
+            canonicalExpressionsMap.putIfAbsent(edgeVariable, new ArrayList<>());
+            originalExpressionMap.put(edgeVariable, edgePatternExpr);
+        }
+
+        // Expand the given pattern group and then prune the invalid pattern groups.
+        List<PatternGroup> validPatternGroups = pruneInvalidPatternGroups(expandPatternGroups(patternGroup));
+        for (PatternGroup validPatternGroup : validPatternGroups) {
+            for (AbstractExpression canonicalExpr : validPatternGroup) {
+                if (canonicalExpr instanceof VertexPatternExpr) {
+                    VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) canonicalExpr;
+                    VariableExpr vertexVariable = vertexPatternExpr.getVariableExpr();
+                    canonicalExpressionsMap.get(vertexVariable).add(deepCopyVisitor.visit(vertexPatternExpr, null));
+
+                } else { // canonicalExpr instanceof EdgePatternExpr
+                    EdgePatternExpr edgePatternExpr = (EdgePatternExpr) canonicalExpr;
+                    EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                    VariableExpr edgeVariable = edgeDescriptor.getVariableExpr();
+                    canonicalExpressionsMap.get(edgeVariable).add(deepCopyVisitor.visit(edgePatternExpr, null));
+
+                    // We must also visit the vertices of our edge (to find internal vertices).
+                    VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+                    VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+                    VariableExpr leftVariable = leftVertex.getVariableExpr();
+                    VariableExpr rightVariable = rightVertex.getVariableExpr();
+                    canonicalExpressionsMap.putIfAbsent(leftVariable, new ArrayList<>());
+                    canonicalExpressionsMap.putIfAbsent(rightVariable, new ArrayList<>());
+                    canonicalExpressionsMap.get(leftVariable).add(deepCopyVisitor.visit(leftVertex, null));
+                    canonicalExpressionsMap.get(rightVariable).add(deepCopyVisitor.visit(rightVertex, null));
+                }
+            }
+        }
+
+        // Propagate the facts of our expression map to the original expressions.
+        final Function<VariableExpr, Stream<VertexPatternExpr>> canonicalVertexStreamProvider =
+                v -> canonicalExpressionsMap.get(v).stream().map(t -> (VertexPatternExpr) t);
+        final Function<VariableExpr, Stream<EdgePatternExpr>> canonicalEdgeStreamProvider =
+                v -> canonicalExpressionsMap.get(v).stream().map(t -> (EdgePatternExpr) t);
+        for (Map.Entry<VariableExpr, AbstractExpression> mapEntry : originalExpressionMap.entrySet()) {
+            if (canonicalExpressionsMap.get(mapEntry.getKey()).isEmpty()) {
+                SourceLocation sourceLocation = mapEntry.getValue().getSourceLocation();
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLocation,
+                        "Encountered graph element that does not conform the queried graph schema!");
+            }
+
+            if (mapEntry.getValue() instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) mapEntry.getValue();
+                vertexPatternExpr.getLabels().clear();
+                Stream<VertexPatternExpr> vertexStream = canonicalVertexStreamProvider.apply(mapEntry.getKey());
+                vertexStream.forEach(v -> vertexPatternExpr.getLabels().addAll(v.getLabels()));
+
+            } else { // originalExpr instanceof EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) mapEntry.getValue();
+                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                Integer maximumHopLength = edgeDescriptor.getMaximumHops();
+
+                // Update our edge labels.
+                edgeDescriptor.getEdgeLabels().clear();
+                Stream<EdgePatternExpr> edgeStream = canonicalEdgeStreamProvider.apply(mapEntry.getKey());
+                edgeStream.forEach(e -> edgeDescriptor.getEdgeLabels().addAll(e.getEdgeDescriptor().getEdgeLabels()));
+
+                // Update our direction, if we have resolved it.
+                Set<EdgeDirection> resolvedDirections = canonicalEdgeStreamProvider.apply(mapEntry.getKey())
+                        .map(e -> e.getEdgeDescriptor().getEdgeDirection()).collect(Collectors.toSet());
+                if (resolvedDirections.size() == 1) {
+                    edgeDescriptor.setEdgeDirection(resolvedDirections.iterator().next());
+                }
+
+                // Update the vertex references of our edge.
+                VariableExpr leftVariable = edgePatternExpr.getLeftVertex().getVariableExpr();
+                VariableExpr rightVariable = edgePatternExpr.getRightVertex().getVariableExpr();
+                edgePatternExpr.getLeftVertex().getLabels().clear();
+                edgePatternExpr.getRightVertex().getLabels().clear();
+                Stream<VertexPatternExpr> leftStream = canonicalVertexStreamProvider.apply(leftVariable);
+                Stream<VertexPatternExpr> rightStream = canonicalVertexStreamProvider.apply(rightVariable);
+                leftStream.forEach(v -> edgePatternExpr.getLeftVertex().getLabels().addAll(v.getLabels()));
+                rightStream.forEach(v -> edgePatternExpr.getRightVertex().getLabels().addAll(v.getLabels()));
+                if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH && maximumHopLength != 1) {
+                    VariableExpr internalVariable = edgePatternExpr.getInternalVertex().getVariableExpr();
+                    edgePatternExpr.getInternalVertex().getLabels().clear();
+                    Stream<VertexPatternExpr> internalStream = canonicalVertexStreamProvider.apply(internalVariable);
+                    internalStream.forEach(v -> edgePatternExpr.getInternalVertex().getLabels().addAll(v.getLabels()));
+                }
+            }
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IInternalVertexSupplier.java
similarity index 79%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IInternalVertexSupplier.java
index 4509792..931f979 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IInternalVertexSupplier.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.lang.rewrite.resolve;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 
 @FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+public interface IInternalVertexSupplier {
+    VertexPatternExpr get() throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IPatternGroupResolver.java
similarity index 79%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IPatternGroupResolver.java
index 4509792..c0129fb 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IPatternGroupResolver.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+package org.apache.asterix.graphix.lang.rewrite.resolve;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.struct.PatternGroup;
 
 @FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
+public interface IPatternGroupResolver {
+    void resolve(PatternGroup patternGroup) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/SchemaKnowledgeTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/SchemaKnowledgeTable.java
new file mode 100644
index 0000000..e0eb8ad
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/SchemaKnowledgeTable.java
@@ -0,0 +1,227 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.resolve;
+
+import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.metadata.entity.schema.Edge;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.metadata.MetadataTransactionContext;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+
+/**
+ * A collection of ground truths, derived from the graph schema (either a {@link Graph} or {@link GraphConstructor}).
+ */
+public class SchemaKnowledgeTable implements Iterable<SchemaKnowledgeTable.KnowledgeRecord> {
+    private final Set<KnowledgeRecord> knowledgeRecordSet = new HashSet<>();
+    private final Set<VertexIdentifier> vertexIdentifierSet = new HashSet<>();
+    private final Set<EdgeIdentifier> edgeIdentifierSet = new HashSet<>();
+    private final GraphIdentifier graphIdentifier;
+
+    public SchemaKnowledgeTable(FromGraphClause fromGraphClause, GraphixRewritingContext graphixRewritingContext)
+            throws CompilationException {
+        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+        DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
+                ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
+        graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
+
+        // Establish our schema knowledge.
+        GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
+        if (graphConstructor == null) {
+            Identifier graphName = fromGraphClause.getGraphName();
+
+            // First, try to find our graph inside our declared graph set.
+            Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
+            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
+            if (declaredGraph != null) {
+                initializeWithGraphConstructor(declaredGraph.getGraphConstructor());
+
+            } else {
+                // Otherwise, fetch the graph from our metadata.
+                try {
+                    MetadataTransactionContext metadataTxnContext = metadataProvider.getMetadataTxnContext();
+                    Graph graphFromMetadata = getGraph(metadataTxnContext, dataverseName, graphName.getValue());
+                    if (graphFromMetadata == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                                "Graph " + graphName.getValue() + " does not exist.");
+                    }
+                    initializeWithMetadataGraph(graphFromMetadata);
+
+                } catch (AlgebricksException e) {
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                            "Graph " + graphName.getValue() + " does not exist.");
+                }
+            }
+
+        } else {
+            initializeWithGraphConstructor(graphConstructor);
+        }
+    }
+
+    private void initializeWithMetadataGraph(Graph graph) {
+        for (Vertex vertex : graph.getGraphSchema().getVertices()) {
+            VertexIdentifier vertexIdentifier = vertex.getIdentifier();
+            vertexIdentifierSet.add(vertexIdentifier);
+        }
+        for (Edge edge : graph.getGraphSchema().getEdges()) {
+            ElementLabel sourceLabel = edge.getSourceLabel();
+            ElementLabel edgeLabel = edge.getLabel();
+            ElementLabel destLabel = edge.getDestinationLabel();
+
+            // Build our source and destination vertex identifiers.
+            VertexIdentifier sourceIdentifier = new VertexIdentifier(graphIdentifier, sourceLabel);
+            VertexIdentifier destIdentifier = new VertexIdentifier(graphIdentifier, destLabel);
+            vertexIdentifierSet.add(sourceIdentifier);
+            vertexIdentifierSet.add(destIdentifier);
+            edgeIdentifierSet.add(edge.getIdentifier());
+
+            // Update our knowledge set.
+            knowledgeRecordSet.add(new KnowledgeRecord(sourceLabel, edgeLabel, destLabel));
+        }
+    }
+
+    private void initializeWithGraphConstructor(GraphConstructor graphConstructor) {
+        for (GraphConstructor.VertexConstructor vertexElement : graphConstructor.getVertexElements()) {
+            VertexIdentifier vertexIdentifier = new VertexIdentifier(graphIdentifier, vertexElement.getLabel());
+            vertexIdentifierSet.add(vertexIdentifier);
+        }
+        for (GraphConstructor.EdgeConstructor edgeElement : graphConstructor.getEdgeElements()) {
+            ElementLabel sourceLabel = edgeElement.getSourceLabel();
+            ElementLabel edgeLabel = edgeElement.getEdgeLabel();
+            ElementLabel destLabel = edgeElement.getDestinationLabel();
+
+            // Build our edge identifiers, and source & destination vertex identifiers.
+            VertexIdentifier sourceIdentifier = new VertexIdentifier(graphIdentifier, sourceLabel);
+            VertexIdentifier destIdentifier = new VertexIdentifier(graphIdentifier, destLabel);
+            EdgeIdentifier edgeIdentifier = new EdgeIdentifier(graphIdentifier, sourceLabel, edgeLabel, destLabel);
+            vertexIdentifierSet.add(sourceIdentifier);
+            vertexIdentifierSet.add(destIdentifier);
+            edgeIdentifierSet.add(edgeIdentifier);
+
+            // Update our knowledge set.
+            knowledgeRecordSet.add(new KnowledgeRecord(sourceLabel, edgeLabel, destLabel));
+        }
+    }
+
+    public Set<ElementLabel> getVertexLabels() {
+        return vertexIdentifierSet.stream().map(VertexIdentifier::getVertexLabel).collect(Collectors.toSet());
+    }
+
+    public Set<ElementLabel> getEdgeLabels() {
+        return edgeIdentifierSet.stream().map(EdgeIdentifier::getEdgeLabel).collect(Collectors.toSet());
+    }
+
+    public boolean isValidEdge(EdgePatternExpr edgePatternExpr) {
+        for (ElementLabel leftLabel : edgePatternExpr.getLeftVertex().getLabels()) {
+            for (ElementLabel rightLabel : edgePatternExpr.getRightVertex().getLabels()) {
+                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT) {
+                        EdgeIdentifier id = new EdgeIdentifier(graphIdentifier, leftLabel, edgeLabel, rightLabel);
+                        if (!edgeIdentifierSet.contains(id)) {
+                            return false;
+                        }
+                    }
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+                        EdgeIdentifier id = new EdgeIdentifier(graphIdentifier, rightLabel, edgeLabel, leftLabel);
+                        if (!edgeIdentifierSet.contains(id)) {
+                            return false;
+                        }
+                    }
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public Iterator<KnowledgeRecord> iterator() {
+        return knowledgeRecordSet.iterator();
+    }
+
+    public static class KnowledgeRecord {
+        private final ElementLabel sourceElementLabel;
+        private final ElementLabel destElementLabel;
+        private final ElementLabel edgeLabel;
+
+        public KnowledgeRecord(ElementLabel sourceElementLabel, ElementLabel edgeLabel, ElementLabel destElementLabel) {
+            this.sourceElementLabel = Objects.requireNonNull(sourceElementLabel);
+            this.destElementLabel = Objects.requireNonNull(destElementLabel);
+            this.edgeLabel = Objects.requireNonNull(edgeLabel);
+        }
+
+        public ElementLabel getSourceVertexLabel() {
+            return sourceElementLabel;
+        }
+
+        public ElementLabel getDestVertexLabel() {
+            return destElementLabel;
+        }
+
+        public ElementLabel getEdgeLabel() {
+            return edgeLabel;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s)-[%s]->(%s)", sourceElementLabel, edgeLabel, destElementLabel);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (!(object instanceof KnowledgeRecord)) {
+                return false;
+            }
+            KnowledgeRecord target = (KnowledgeRecord) object;
+            return sourceElementLabel.equals(target.sourceElementLabel)
+                    && destElementLabel.equals(target.destElementLabel) && edgeLabel.equals(target.edgeLabel);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(sourceElementLabel, destElementLabel, edgeLabel);
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/CanonicalElementUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/CanonicalElementUtil.java
new file mode 100644
index 0000000..36d1fc0
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/CanonicalElementUtil.java
@@ -0,0 +1,221 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.util;
+
+import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.EdgeDirection;
+import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.PatternType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.resolve.IInternalVertexSupplier;
+import org.apache.asterix.graphix.lang.rewrite.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.paukov.combinatorics3.Generator;
+
+public final class CanonicalElementUtil {
+    public static Set<ElementLabel> expandElementLabels(AbstractExpression originalExpr,
+            Set<ElementLabel> elementLabels, SchemaKnowledgeTable schemaKnowledgeTable) {
+        Set<ElementLabel> schemaLabels = (originalExpr instanceof VertexPatternExpr)
+                ? schemaKnowledgeTable.getVertexLabels() : schemaKnowledgeTable.getEdgeLabels();
+        if (elementLabels.isEmpty()) {
+            // If our set is empty, then return all the element labels associated with our schema.
+            return schemaLabels;
+        }
+
+        // If we have any negated element labels, then we return the difference between our schema and this set.
+        Set<String> negatedElementLabels = elementLabels.stream().filter(ElementLabel::isNegated)
+                .map(ElementLabel::getLabelName).collect(Collectors.toSet());
+        if (!negatedElementLabels.isEmpty()) {
+            return schemaLabels.stream().filter(s -> !negatedElementLabels.contains(s.getLabelName()))
+                    .collect(Collectors.toSet());
+        }
+
+        // Otherwise, we return our input set of labels.
+        return elementLabels;
+    }
+
+    public static Set<EdgeDirection> expandEdgeDirection(EdgeDescriptor edgeDescriptor) {
+        Set<EdgeDirection> edgeDirectionList = new HashSet<>();
+        if (edgeDescriptor.getEdgeDirection() != EdgeDirection.RIGHT_TO_LEFT) {
+            edgeDirectionList.add(EdgeDirection.LEFT_TO_RIGHT);
+        }
+        if (edgeDescriptor.getEdgeDirection() != EdgeDirection.LEFT_TO_RIGHT) {
+            edgeDirectionList.add(EdgeDirection.RIGHT_TO_LEFT);
+        }
+        return edgeDirectionList;
+    }
+
+    public static List<List<EdgePatternExpr>> expandFixedPathPattern(int pathLength, EdgePatternExpr edgePattern,
+            Set<ElementLabel> edgeLabels, Set<ElementLabel> vertexLabels, Set<EdgeDirection> edgeDirections,
+            GraphixDeepCopyVisitor deepCopyVisitor, IInternalVertexSupplier internalVertexSupplier)
+            throws CompilationException {
+        VertexPatternExpr leftVertex = edgePattern.getLeftVertex();
+        VertexPatternExpr rightVertex = edgePattern.getRightVertex();
+        VariableExpr edgeVariableExpr = edgePattern.getEdgeDescriptor().getVariableExpr();
+
+        // Generate all possible edge label K-permutations w/ repetitions.
+        List<List<ElementLabel>> edgeLabelPermutations = new ArrayList<>();
+        Generator.combination(edgeLabels).multi(pathLength).stream()
+                .forEach(c -> Generator.permutation(c).simple().forEach(edgeLabelPermutations::add));
+
+        // Generate all possible direction K-permutations w/ repetitions.
+        List<List<EdgeDirection>> directionPermutations = new ArrayList<>();
+        Generator.combination(edgeDirections).multi(pathLength).stream()
+                .forEach(c -> Generator.permutation(c).simple().forEach(directionPermutations::add));
+
+        // Special case: if we have a path of length one, we do not need to permute our vertices.
+        if (pathLength == 1) {
+            List<List<EdgePatternExpr>> expandedPathPatterns = new ArrayList<>();
+            for (List<ElementLabel> edgeLabelPermutation : edgeLabelPermutations) {
+                ElementLabel edgeLabel = edgeLabelPermutation.get(0);
+                for (List<EdgeDirection> directionPermutation : directionPermutations) {
+                    EdgeDirection edgeDirection = directionPermutation.get(0);
+                    EdgeDescriptor edgeDescriptor = new EdgeDescriptor(edgeDirection, PatternType.EDGE,
+                            Set.of(edgeLabel), null, deepCopyVisitor.visit(edgeVariableExpr, null), null, null);
+                    expandedPathPatterns.add(List.of(new EdgePatternExpr(leftVertex, rightVertex, edgeDescriptor)));
+                }
+            }
+            return expandedPathPatterns;
+        }
+
+        // Otherwise... generate all possible (K-1)-permutations w/ repetitions.
+        List<List<ElementLabel>> vertexLabelPermutations = new ArrayList<>();
+        Generator.combination(vertexLabels).multi(pathLength - 1).stream()
+                .forEach(c -> Generator.permutation(c).simple().forEach(vertexLabelPermutations::add));
+
+        // ... and perform a cartesian product of all three sets above.
+        List<List<EdgePatternExpr>> expandedPathPatterns = new ArrayList<>();
+        for (List<ElementLabel> edgeLabelPermutation : edgeLabelPermutations) {
+            for (List<EdgeDirection> directionPermutation : directionPermutations) {
+                for (List<ElementLabel> vertexLabelPermutation : vertexLabelPermutations) {
+                    Iterator<ElementLabel> edgeLabelIterator = edgeLabelPermutation.iterator();
+                    Iterator<EdgeDirection> directionIterator = directionPermutation.iterator();
+                    Iterator<ElementLabel> vertexLabelIterator = vertexLabelPermutation.iterator();
+
+                    // Build one path.
+                    List<EdgePatternExpr> pathPattern = new ArrayList<>();
+                    VertexPatternExpr previousRightVertex = null;
+                    for (int i = 0; edgeLabelIterator.hasNext(); i++) {
+                        Set<ElementLabel> edgeLabelSet = Set.of(edgeLabelIterator.next());
+                        EdgeDirection edgeDirection = directionIterator.next();
+
+                        // Determine our left vertex.
+                        VertexPatternExpr workingLeftVertex;
+                        if (i == 0) {
+                            workingLeftVertex = deepCopyVisitor.visit(leftVertex, null);
+                        } else {
+                            workingLeftVertex = deepCopyVisitor.visit(previousRightVertex, null);
+                        }
+
+                        // Determine our right vertex.
+                        VertexPatternExpr workingRightVertex;
+                        if (!edgeLabelIterator.hasNext()) {
+                            workingRightVertex = deepCopyVisitor.visit(rightVertex, null);
+
+                        } else {
+                            workingRightVertex = internalVertexSupplier.get();
+                            workingRightVertex.getLabels().clear();
+                            workingRightVertex.getLabels().add(vertexLabelIterator.next());
+                        }
+                        previousRightVertex = workingRightVertex;
+
+                        // And build the edge to add to our path.
+                        EdgeDescriptor edgeDescriptor = new EdgeDescriptor(edgeDirection, PatternType.EDGE,
+                                edgeLabelSet, null, deepCopyVisitor.visit(edgeVariableExpr, null), null, null);
+                        pathPattern.add(new EdgePatternExpr(workingLeftVertex, workingRightVertex, edgeDescriptor));
+                    }
+                    expandedPathPatterns.add(pathPattern);
+                }
+            }
+        }
+        return expandedPathPatterns;
+    }
+
+    public static List<EdgePatternExpr> deepCopyPathPattern(List<EdgePatternExpr> pathPattern,
+            GraphixDeepCopyVisitor deepCopyVisitor) throws CompilationException {
+        List<EdgePatternExpr> deepCopyEdgeList = new ArrayList<>();
+        for (EdgePatternExpr edgePatternExpr : pathPattern) {
+            deepCopyEdgeList.add(deepCopyVisitor.visit(edgePatternExpr, null));
+        }
+        return deepCopyEdgeList;
+    }
+
+    public static void replaceVertexInIterator(Map<VertexPatternExpr, VertexPatternExpr> replaceMap,
+            ListIterator<VertexPatternExpr> elementIterator, GraphixDeepCopyVisitor deepCopyVisitor)
+            throws CompilationException {
+        while (elementIterator.hasNext()) {
+            VertexPatternExpr nextElement = elementIterator.next();
+            if (replaceMap.containsKey(nextElement)) {
+                VertexPatternExpr replacementElement = replaceMap.get(nextElement);
+                elementIterator.set((VertexPatternExpr) replacementElement.accept(deepCopyVisitor, null));
+            }
+        }
+    }
+
+    public static void replaceEdgeInIterator(Map<EdgePatternExpr, EdgePatternExpr> replaceMap,
+            ListIterator<EdgePatternExpr> elementIterator, GraphixDeepCopyVisitor deepCopyVisitor)
+            throws CompilationException {
+        // Build a map of vertices to vertices from our replace map.
+        Map<VertexPatternExpr, VertexPatternExpr> replaceVertexMap = new HashMap<>();
+        for (Map.Entry<EdgePatternExpr, EdgePatternExpr> mapEntry : replaceMap.entrySet()) {
+            VertexPatternExpr keyLeftVertex = mapEntry.getKey().getLeftVertex();
+            VertexPatternExpr keyRightVertex = mapEntry.getKey().getRightVertex();
+            VertexPatternExpr valueLeftVertex = mapEntry.getValue().getLeftVertex();
+            VertexPatternExpr valueRightVertex = mapEntry.getValue().getRightVertex();
+            replaceVertexMap.put(keyLeftVertex, valueLeftVertex);
+            replaceVertexMap.put(keyRightVertex, valueRightVertex);
+        }
+
+        while (elementIterator.hasNext()) {
+            EdgePatternExpr nextElement = elementIterator.next();
+            if (replaceMap.containsKey(nextElement)) {
+                EdgePatternExpr replacementElement = replaceMap.get(nextElement);
+                elementIterator.set((EdgePatternExpr) replacementElement.accept(deepCopyVisitor, null));
+
+            } else {
+                if (replaceVertexMap.containsKey(nextElement.getLeftVertex())) {
+                    VertexPatternExpr replaceLeftVertex = replaceVertexMap.get(nextElement.getLeftVertex());
+                    nextElement.setLeftVertex(deepCopyVisitor.visit(replaceLeftVertex, null));
+                }
+                if (replaceVertexMap.containsKey(nextElement.getRightVertex())) {
+                    VertexPatternExpr replaceRightVertex = replaceVertexMap.get(nextElement.getRightVertex());
+                    nextElement.setRightVertex(deepCopyVisitor.visit(replaceRightVertex, null));
+                }
+            }
+        }
+    }
+
+    private CanonicalElementUtil() {
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/LowerRewritingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/LowerRewritingUtil.java
similarity index 90%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/LowerRewritingUtil.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/LowerRewritingUtil.java
index 616138e..78d7aea 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/LowerRewritingUtil.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/util/LowerRewritingUtil.java
@@ -16,19 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.util;
+package org.apache.asterix.graphix.lang.rewrite.util;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrite.visitor.GraphixDeepCopyVisitor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
 import org.apache.asterix.lang.common.expression.OperatorExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 
 public final class LowerRewritingUtil {
     public static Expression buildConnectedClauses(List<Expression> clauses, OperatorType connector) {
@@ -59,9 +59,10 @@
 
     public static List<FieldAccessor> buildAccessorList(Expression startingExpr, List<List<String>> fieldNames)
             throws CompilationException {
+        GraphixDeepCopyVisitor deepCopyVisitor = new GraphixDeepCopyVisitor();
         List<FieldAccessor> fieldAccessors = new ArrayList<>();
         for (List<String> nestedField : fieldNames) {
-            Expression copiedStartingExpr = (Expression) SqlppRewriteUtil.deepCopy(startingExpr);
+            Expression copiedStartingExpr = (Expression) startingExpr.accept(deepCopyVisitor, null);
             FieldAccessor workingAccessor = new FieldAccessor(copiedStartingExpr, new Identifier(nestedField.get(0)));
             for (String field : nestedField.subList(1, nestedField.size())) {
                 workingAccessor = new FieldAccessor(workingAccessor, new Identifier(field));
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/AmbiguousElementVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/AmbiguousElementVisitor.java
new file mode 100644
index 0000000..da9bff9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/AmbiguousElementVisitor.java
@@ -0,0 +1,166 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.algebra.compiler.option.ElementEvaluationOption;
+import org.apache.asterix.graphix.algebra.compiler.option.IGraphixCompilerOption;
+import org.apache.asterix.graphix.lang.annotation.ElementEvaluationAnnotation;
+import org.apache.asterix.graphix.lang.annotation.SubqueryVertexJoinAnnotation;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.canonical.CanonicalElementGeneratorFactory;
+import org.apache.asterix.graphix.lang.rewrite.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+
+/**
+ * @see QueryCanonicalizationVisitor
+ */
+public class AmbiguousElementVisitor extends AbstractGraphixQueryVisitor {
+    private final Map<AbstractExpression, Context> elementContextMap;
+    private final Deque<CanonicalElementGeneratorFactory> factoryStack;
+    private final Deque<SelectBlock> selectBlockStack;
+
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final ElementEvaluationOption defaultElementEvaluation;
+
+    private static class Context {
+        final CanonicalElementGeneratorFactory generatorFactory;
+        final ElementEvaluationOption elementEvaluationOption;
+        final SelectBlock sourceSelectBlock;
+
+        private Context(CanonicalElementGeneratorFactory generatorFactory,
+                ElementEvaluationOption elementEvaluationOption, SelectBlock sourceSelectBlock) {
+            this.generatorFactory = generatorFactory;
+            this.elementEvaluationOption = elementEvaluationOption;
+            this.sourceSelectBlock = sourceSelectBlock;
+        }
+    }
+
+    public AmbiguousElementVisitor(GraphixRewritingContext graphixRewritingContext) throws CompilationException {
+        IGraphixCompilerOption setting = graphixRewritingContext.getSetting(ElementEvaluationOption.OPTION_KEY_NAME);
+        this.defaultElementEvaluation = (ElementEvaluationOption) setting;
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.elementContextMap = new HashMap<>();
+        this.selectBlockStack = new ArrayDeque<>();
+        this.factoryStack = new ArrayDeque<>();
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        selectBlockStack.push(selectBlock);
+        super.visit(selectBlock, arg);
+        selectBlockStack.pop();
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        SchemaKnowledgeTable knowledgeTable = new SchemaKnowledgeTable(fromGraphClause, graphixRewritingContext);
+        factoryStack.push(new CanonicalElementGeneratorFactory(graphixRewritingContext, knowledgeTable));
+        super.visit(fromGraphClause, arg);
+        factoryStack.pop();
+        return null;
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg) throws CompilationException {
+        Set<VariableExpr> visitedVertices = new HashSet<>();
+        for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
+            edgeExpression.accept(this, arg);
+            visitedVertices.add(edgeExpression.getLeftVertex().getVariableExpr());
+            visitedVertices.add(edgeExpression.getRightVertex().getVariableExpr());
+        }
+        for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
+            if (!visitedVertices.contains(vertexExpression.getVariableExpr())) {
+                vertexExpression.accept(this, arg);
+            }
+        }
+        return pathPatternExpr;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        if ((edgeDescriptor.getEdgeLabels().size() + leftVertex.getLabels().size() + rightVertex.getLabels().size()) > 3
+                || edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
+                || edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED) {
+            elementContextMap.put(edgePatternExpr, new Context(factoryStack.peek(),
+                    getEvaluationFromAnnotation(edgePatternExpr), selectBlockStack.peek()));
+        }
+        return super.visit(edgePatternExpr, arg);
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
+        if (vertexPatternExpr.getLabels().size() > 1
+                && vertexPatternExpr.findHint(SubqueryVertexJoinAnnotation.class) == null) {
+            elementContextMap.put(vertexPatternExpr, new Context(factoryStack.peek(),
+                    ElementEvaluationOption.EXPAND_AND_UNION, selectBlockStack.peek()));
+        }
+        return super.visit(vertexPatternExpr, arg);
+    }
+
+    private ElementEvaluationOption getEvaluationFromAnnotation(AbstractExpression expression) {
+        ElementEvaluationAnnotation hint = expression.findHint(ElementEvaluationAnnotation.class);
+        if (hint == null) {
+            return defaultElementEvaluation;
+
+        } else if (hint.getKind() == ElementEvaluationAnnotation.Kind.EXPAND_AND_UNION) {
+            return ElementEvaluationOption.EXPAND_AND_UNION;
+
+        } else { // hint.getKind() == ElementEvaluationAnnotation.Kind.SWITCH_AND_CYCLE
+            return ElementEvaluationOption.SWITCH_AND_CYCLE;
+        }
+    }
+
+    public Set<AbstractExpression> getAmbiguousElements() {
+        return elementContextMap.keySet();
+    }
+
+    public CanonicalElementGeneratorFactory getGeneratorFactory(AbstractExpression ambiguousElement) {
+        return elementContextMap.get(ambiguousElement).generatorFactory;
+    }
+
+    public SelectBlock getSourceSelectBlock(AbstractExpression ambiguousElement) {
+        return elementContextMap.get(ambiguousElement).sourceSelectBlock;
+    }
+
+    public ElementEvaluationOption getElementEvaluationOption(AbstractExpression ambiguousElement) {
+        return elementContextMap.get(ambiguousElement).elementEvaluationOption;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementBodyAnalysisVisitor.java
similarity index 90%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementBodyAnalysisVisitor.java
index 448f698..1fca964 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementBodyAnalysisVisitor.java
@@ -16,10 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
@@ -42,6 +43,7 @@
 import org.apache.asterix.lang.sqlpp.clause.SelectClause;
 import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
 import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
 import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
@@ -52,13 +54,15 @@
 /**
  * Perform an analysis of a normalized graph element body (i.e. no Graphix AST nodes) to determine if we can inline
  * this graph element body with the greater SELECT-BLOCK node during lowering.
- * 1. Is this a dataset CALL-EXPR? If so, we can inline this directly.
- * 2. Is this expression a SELECT-EXPR containing a single FROM-TERM w/ possibly only UNNEST clauses? If so, we can
- * inline this expression.
- * 3. Are there are LET-WHERE expressions? We can inline these if the two questions are true.
- * 4. Are there any aggregate functions (or any aggregation)? If so, we cannot inline this expression.
- * 5. Are there any UNION-ALLs? If so, we cannot inline this expression.
- * 6. Are there any ORDER-BY or LIMIT clauses? If so, we cannot inline this expression.
+ * <ol>
+ *  <li>Is this a dataset CALL-EXPR? If so, we can inline this directly.</li>
+ *  <li>Is this expression a SELECT-EXPR containing a single FROM-TERM w/ possibly only UNNEST clauses? If so, we can
+ *  inline this expression.</li>
+ *  <li>Are there are LET-WHERE expressions? We can inline these if the two questions are true.</li>
+ *  <li>Are there any aggregate functions (or any aggregation)? If so, we cannot inline this expression.</li>
+ *  <li>Are there any UNION-ALLs? If so, we cannot inline this expression.</li>
+ *  <li>Are there any ORDER-BY or LIMIT clauses? If so, we cannot inline this expression.</li>
+ * </ol>
  */
 public class ElementBodyAnalysisVisitor extends AbstractSqlppSimpleExpressionVisitor {
     private final ElementBodyAnalysisContext elementBodyAnalysisContext = new ElementBodyAnalysisContext();
@@ -225,7 +229,10 @@
     @Override
     public Expression visit(FromTerm fromTerm, ILangExpression arg) throws CompilationException {
         List<AbstractBinaryCorrelateClause> correlateClauses = fromTerm.getCorrelateClauses();
-        if (correlateClauses.stream().anyMatch(c -> !c.getClauseType().equals(Clause.ClauseType.UNNEST_CLAUSE))) {
+        List<AbstractBinaryCorrelateClause> unnestClauses =
+                correlateClauses.stream().filter(c -> c.getClauseType().equals(Clause.ClauseType.UNNEST_CLAUSE))
+                        .map(c -> (UnnestClause) c).collect(Collectors.toList());
+        if (correlateClauses.size() != unnestClauses.size()) {
             elementBodyAnalysisContext.isExpressionInline = false;
             return null;
 
@@ -233,10 +240,10 @@
             // TODO (GLENN): Add support for positional variables.
             elementBodyAnalysisContext.isExpressionInline = false;
             return null;
-
         }
-        if (!correlateClauses.isEmpty()) {
-            elementBodyAnalysisContext.unnestClauses = correlateClauses;
+
+        if (!unnestClauses.isEmpty()) {
+            elementBodyAnalysisContext.unnestClauses = unnestClauses;
         }
         fromTerm.getLeftExpression().accept(this, arg);
         elementBodyAnalysisContext.fromTermVariable = fromTerm.getLeftVariable();
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementLookupTableVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementLookupTableVisitor.java
similarity index 83%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementLookupTableVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementLookupTableVisitor.java
index 6ba1d87..9bbe3f0 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementLookupTableVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementLookupTableVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import static org.apache.asterix.graphix.lang.parser.GraphElementBodyParser.parse;
 
@@ -28,8 +28,9 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
@@ -37,12 +38,13 @@
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
 import org.apache.asterix.graphix.metadata.entity.schema.Edge;
 import org.apache.asterix.graphix.metadata.entity.schema.Graph;
 import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
@@ -55,7 +57,7 @@
 
 /**
  * Populate the given graph element table, which will hold all referenced {@link GraphElementDeclaration}s. We assume
- * that our graph elements are properly labeled at this point (i.e. {@link StructureResolutionVisitor} must run before
+ * that our graph elements are properly labeled at this point (i.e. {@link PatternGraphGroupVisitor} must run before
  * this).
  */
 public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
@@ -63,7 +65,7 @@
     private final MetadataProvider metadataProvider;
     private final GraphixParserFactory parserFactory;
 
-    private final Set<ElementLabel> referencedVertexLabels = new HashSet<>();
+    private final Set<ElementLabel> referencedElementLabels = new HashSet<>();
     private final Set<ElementLabel> referencedEdgeLabels = new HashSet<>();
     private final ElementLookupTable elementLookupTable;
     private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
@@ -84,14 +86,13 @@
         }
 
         GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
-        GraphIdentifier graphIdentifier = null;
+        GraphIdentifier graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
         if (graphConstructor == null) {
             DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
                     ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
             Identifier graphName = fromGraphClause.getGraphName();
 
             // Our query refers to a named graph. First see if we can find this in our declared graph set.
-            graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
             DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
             if (declaredGraph != null) {
                 graphConstructor = declaredGraph.getGraphConstructor();
@@ -113,7 +114,7 @@
                 }
 
                 for (Vertex vertex : graphFromMetadata.getGraphSchema().getVertices()) {
-                    if (referencedVertexLabels.contains(vertex.getLabel())) {
+                    if (referencedElementLabels.contains(vertex.getLabel())) {
                         GraphElementDeclaration vertexDecl = parse(vertex, parserFactory, warningCollector);
                         elementLookupTable.put(vertex.getIdentifier(), vertexDecl);
                         elementLookupTable.putVertexKey(vertex.getIdentifier(), vertex.getPrimaryKeyFieldNames());
@@ -130,24 +131,17 @@
             }
         }
         if (graphConstructor != null) {
-            if (graphIdentifier == null) {
-                // We have been provided an anonymous graph. Load the referenced elements from our walk.
-                DataverseName defaultDataverse = metadataProvider.getDefaultDataverse().getDataverseName();
-                graphIdentifier = new GraphIdentifier(defaultDataverse, graphConstructor.getInstanceID());
-            }
-
             for (GraphConstructor.VertexConstructor vertex : graphConstructor.getVertexElements()) {
-                if (referencedVertexLabels.contains(vertex.getLabel())) {
-                    GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier,
-                            GraphElementIdentifier.Kind.VERTEX, vertex.getLabel());
+                if (referencedElementLabels.contains(vertex.getLabel())) {
+                    VertexIdentifier identifier = new VertexIdentifier(graphIdentifier, vertex.getLabel());
                     elementLookupTable.put(identifier, new GraphElementDeclaration(identifier, vertex.getExpression()));
                     elementLookupTable.putVertexKey(identifier, vertex.getPrimaryKeyFields());
                 }
             }
             for (GraphConstructor.EdgeConstructor edge : graphConstructor.getEdgeElements()) {
                 if (referencedEdgeLabels.contains(edge.getEdgeLabel())) {
-                    GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier,
-                            GraphElementIdentifier.Kind.EDGE, edge.getEdgeLabel());
+                    EdgeIdentifier identifier = new EdgeIdentifier(graphIdentifier, edge.getSourceLabel(),
+                            edge.getEdgeLabel(), edge.getDestinationLabel());
                     elementLookupTable.put(identifier, new GraphElementDeclaration(identifier, edge.getExpression()));
                     elementLookupTable.putEdgeKeys(identifier, edge.getSourceKeyFields(),
                             edge.getDestinationKeyFields());
@@ -164,8 +158,8 @@
                     "EdgePatternExpr found without labels. Elements should have been resolved earlier.");
         }
         referencedEdgeLabels.addAll(edgeDescriptor.getEdgeLabels());
-        for (VertexPatternExpr internalVertex : edgeExpression.getInternalVertices()) {
-            internalVertex.accept(this, arg);
+        if (edgeExpression.getInternalVertex() != null && edgeDescriptor.getMaximumHops() != 1) {
+            edgeExpression.getInternalVertex().accept(this, arg);
         }
         return edgeExpression;
     }
@@ -175,7 +169,7 @@
             throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, vertexExpression.getSourceLocation(),
                     "VertexPatternExpr found without labels. Elements should have been resolved earlier.");
         }
-        referencedVertexLabels.addAll(vertexExpression.getLabels());
+        referencedElementLabels.addAll(vertexExpression.getLabels());
         return vertexExpression;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementSubstitutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementSubstitutionVisitor.java
new file mode 100644
index 0000000..16c5200
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/ElementSubstitutionVisitor.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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+
+/**
+ * Visitor to replace a {@link VertexPatternExpr}, a {@link EdgePatternExpr}, or a {@link PathPatternExpr}.
+ */
+public class ElementSubstitutionVisitor<T extends AbstractExpression> extends AbstractGraphixQueryVisitor {
+    private final T replacementElementExpression;
+    private final T originalElementExpression;
+
+    public ElementSubstitutionVisitor(T replacementElementExpression, T originalElementExpression) {
+        this.replacementElementExpression = replacementElementExpression;
+        this.originalElementExpression = originalElementExpression;
+        if (!((replacementElementExpression instanceof VertexPatternExpr)
+                || (replacementElementExpression instanceof EdgePatternExpr)
+                || (replacementElementExpression instanceof PathPatternExpr))) {
+            throw new IllegalArgumentException(
+                    "Only VertexPatternExpr, EdgePatternExpr, or PathPatternExpr " + "instances should be given.");
+        }
+    }
+
+    @Override
+    public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
+        if (replacementElementExpression instanceof PathPatternExpr) {
+            ListIterator<PathPatternExpr> pathIterator = matchClause.getPathExpressions().listIterator();
+            while (pathIterator.hasNext()) {
+                PathPatternExpr pathPatternExpr = pathIterator.next();
+                if (pathPatternExpr.equals(originalElementExpression)) {
+                    pathIterator.set((PathPatternExpr) replacementElementExpression);
+                    break;
+                }
+            }
+            return null;
+        }
+        return super.visit(matchClause, arg);
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg) throws CompilationException {
+        if (replacementElementExpression instanceof VertexPatternExpr) {
+            ListIterator<VertexPatternExpr> vertexExprIterator = pathPatternExpr.getVertexExpressions().listIterator();
+            while (vertexExprIterator.hasNext()) {
+                VertexPatternExpr workingVertexExpression = vertexExprIterator.next();
+                if (workingVertexExpression.equals(originalElementExpression)) {
+                    vertexExprIterator.set((VertexPatternExpr) replacementElementExpression);
+                    break;
+                }
+            }
+            List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
+            for (EdgePatternExpr workingEdgeExpression : edgeExpressions) {
+                if (workingEdgeExpression.getLeftVertex().equals(originalElementExpression)) {
+                    workingEdgeExpression.setLeftVertex((VertexPatternExpr) replacementElementExpression);
+                }
+                if (workingEdgeExpression.getRightVertex().equals(originalElementExpression)) {
+                    workingEdgeExpression.setRightVertex((VertexPatternExpr) replacementElementExpression);
+                }
+            }
+
+        } else { // replacementElementExpression instanceof EdgePatternExpr
+            ListIterator<EdgePatternExpr> edgeExprIterator = pathPatternExpr.getEdgeExpressions().listIterator();
+            while (edgeExprIterator.hasNext()) {
+                EdgePatternExpr workingEdgeExpression = edgeExprIterator.next();
+                if (workingEdgeExpression.equals(originalElementExpression)) {
+                    edgeExprIterator.set((EdgePatternExpr) replacementElementExpression);
+                    break;
+                }
+            }
+        }
+        return pathPatternExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/FunctionResolutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/FunctionResolutionVisitor.java
similarity index 90%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/FunctionResolutionVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/FunctionResolutionVisitor.java
index 689f58f..961c270 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/FunctionResolutionVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/FunctionResolutionVisitor.java
@@ -14,12 +14,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.graphix.function.GraphixFunctionResolver;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
@@ -49,8 +50,9 @@
         FunctionSignature functionSignature = graphixFunctionResolver.resolve(callExpr, allowNonStoredUdfCalls);
         if (functionSignature != null) {
             callExpr.setFunctionSignature(functionSignature);
+            return super.visit(callExpr, arg);
         }
-        return super.visit(callExpr, arg);
+        return sqlppFunctionCallResolverVisitor.visit(callExpr, arg);
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixDeepCopyVisitor.java
similarity index 66%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixDeepCopyVisitor.java
index 8a4b93c..2280e75 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixDeepCopyVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -25,7 +25,6 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
@@ -37,13 +36,16 @@
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.clause.GroupbyClause;
 import org.apache.asterix.lang.common.clause.LetClause;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.clause.SelectClause;
 import org.apache.asterix.lang.sqlpp.visitor.DeepCopyVisitor;
 
@@ -52,23 +54,31 @@
  */
 public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixLangVisitor<ILangExpression, Void> {
     @Override
-    public GraphSelectBlock visit(GraphSelectBlock graphSelectBlock, Void arg) throws CompilationException {
-        SelectClause clonedSelectClause = this.visit(graphSelectBlock.getSelectClause(), arg);
-        FromGraphClause clonedFromGraphClause = this.visit(graphSelectBlock.getFromGraphClause(), arg);
+    public SelectBlock visit(SelectBlock selectBlock, Void arg) throws CompilationException {
+        SelectClause clonedSelectClause = this.visit(selectBlock.getSelectClause(), arg);
+        FromClause clonedFromClause = null;
+        if (selectBlock.hasFromClause() && selectBlock.getFromClause() instanceof FromGraphClause) {
+            clonedFromClause = this.visit((FromGraphClause) selectBlock.getFromClause(), arg);
+
+        } else if (selectBlock.hasFromClause()) {
+            clonedFromClause = super.visit(selectBlock.getFromClause(), arg);
+        }
         GroupbyClause clonedGroupByClause = null;
-        if (graphSelectBlock.hasGroupbyClause()) {
-            clonedGroupByClause = this.visit(graphSelectBlock.getGroupbyClause(), arg);
+        if (selectBlock.hasGroupbyClause()) {
+            clonedGroupByClause = this.visit(selectBlock.getGroupbyClause(), arg);
         }
         List<AbstractClause> clonedLetWhereClauses = new ArrayList<>();
         List<AbstractClause> clonedLetHavingClauses = new ArrayList<>();
-        for (AbstractClause letWhereClause : graphSelectBlock.getLetWhereList()) {
+        for (AbstractClause letWhereClause : selectBlock.getLetWhereList()) {
             clonedLetWhereClauses.add((AbstractClause) letWhereClause.accept(this, arg));
         }
-        for (AbstractClause letHavingClause : graphSelectBlock.getLetHavingListAfterGroupby()) {
+        for (AbstractClause letHavingClause : selectBlock.getLetHavingListAfterGroupby()) {
             clonedLetHavingClauses.add((AbstractClause) letHavingClause.accept(this, arg));
         }
-        return new GraphSelectBlock(clonedSelectClause, clonedFromGraphClause, clonedLetWhereClauses,
+        SelectBlock clonedSelectBlock = new SelectBlock(clonedSelectClause, clonedFromClause, clonedLetWhereClauses,
                 clonedGroupByClause, clonedLetHavingClauses);
+        clonedSelectBlock.setSourceLocation(selectBlock.getSourceLocation());
+        return clonedSelectBlock;
     }
 
     @Override
@@ -81,14 +91,17 @@
         for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
             clonedMatchClauses.add(this.visit(matchClause, arg));
         }
+        FromGraphClause clonedFromGraphClause;
         if (fromGraphClause.getGraphConstructor() != null) {
             GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
-            return new FromGraphClause(graphConstructor, clonedMatchClauses, clonedCorrelateClauses);
+            clonedFromGraphClause = new FromGraphClause(graphConstructor, clonedMatchClauses, clonedCorrelateClauses);
 
         } else {
-            return new FromGraphClause(fromGraphClause.getDataverseName(), fromGraphClause.getGraphName(),
-                    clonedMatchClauses, clonedCorrelateClauses);
+            clonedFromGraphClause = new FromGraphClause(fromGraphClause.getDataverseName(),
+                    fromGraphClause.getGraphName(), clonedMatchClauses, clonedCorrelateClauses);
         }
+        clonedFromGraphClause.setSourceLocation(fromGraphClause.getSourceLocation());
+        return clonedFromGraphClause;
     }
 
     @Override
@@ -97,7 +110,9 @@
         for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
             clonedPathExpression.add(this.visit(pathExpression, arg));
         }
-        return new MatchClause(clonedPathExpression, matchClause.getMatchType());
+        MatchClause clonedMatchClause = new MatchClause(clonedPathExpression, matchClause.getMatchType());
+        clonedMatchClause.setSourceLocation(matchClause.getSourceLocation());
+        return clonedMatchClause;
     }
 
     @Override
@@ -110,26 +125,31 @@
         }
 
         // Only visit dangling vertices in our edge.
-        Set<VarIdentifier> visitedVertices = new HashSet<>();
+        Set<VariableExpr> visitedVertices = new HashSet<>();
         for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
             EdgePatternExpr clonedEdgeExpression = this.visit(edgeExpression, arg);
             clonedEdgeExpressions.add(clonedEdgeExpression);
-            clonedVertexExpressions.add(clonedEdgeExpression.getLeftVertex());
-            clonedVertexExpressions.add(clonedEdgeExpression.getRightVertex());
-            visitedVertices.add(clonedEdgeExpression.getRightVertex().getVariableExpr().getVar());
-            visitedVertices.add(clonedEdgeExpression.getLeftVertex().getVariableExpr().getVar());
+            clonedEdgeExpression.setSourceLocation(edgeExpression.getSourceLocation());
+            VertexPatternExpr clonedLeftVertex = clonedEdgeExpression.getLeftVertex();
+            VertexPatternExpr clonedRightVertex = clonedEdgeExpression.getRightVertex();
+            clonedVertexExpressions.add(clonedLeftVertex);
+            clonedVertexExpressions.add(clonedRightVertex);
+            visitedVertices.add(clonedLeftVertex.getVariableExpr());
+            visitedVertices.add(clonedRightVertex.getVariableExpr());
         }
         for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
-            if (!visitedVertices.contains(vertexExpression.getVariableExpr().getVar())) {
+            if (!visitedVertices.contains(vertexExpression.getVariableExpr())) {
                 VertexPatternExpr clonedVertexExpression = this.visit(vertexExpression, arg);
+                clonedVertexExpression.setSourceLocation(vertexExpression.getSourceLocation());
                 clonedVertexExpressions.add(clonedVertexExpression);
-                visitedVertices.add(clonedVertexExpression.getVariableExpr().getVar());
+                visitedVertices.add(clonedVertexExpression.getVariableExpr());
             }
         }
 
         // Clone our sub-path expressions.
         PathPatternExpr clonedPathPatternExpr =
                 new PathPatternExpr(clonedVertexExpressions, clonedEdgeExpressions, clonedVariableExpr);
+        clonedPathPatternExpr.setSourceLocation(pathPatternExpr.getSourceLocation());
         for (LetClause letClause : pathPatternExpr.getReboundSubPathList()) {
             clonedPathPatternExpr.getReboundSubPathList().add(this.visit(letClause, arg));
         }
@@ -141,23 +161,30 @@
         // Clone our expressions.
         VertexPatternExpr clonedLeftVertex = this.visit(edgePatternExpr.getLeftVertex(), arg);
         VertexPatternExpr clonedRightVertex = this.visit(edgePatternExpr.getRightVertex(), arg);
-        List<VertexPatternExpr> clonedInternalVertices = new ArrayList<>();
-        for (VertexPatternExpr internalVertex : edgePatternExpr.getInternalVertices()) {
-            clonedInternalVertices.add(this.visit(internalVertex, arg));
+        VertexPatternExpr clonedInternalVertex = null;
+        if (edgePatternExpr.getInternalVertex() != null) {
+            clonedInternalVertex = this.visit(edgePatternExpr.getInternalVertex(), arg);
         }
         VariableExpr clonedVariableExpr = null;
         if (edgePatternExpr.getEdgeDescriptor().getVariableExpr() != null) {
             clonedVariableExpr = this.visit(edgePatternExpr.getEdgeDescriptor().getVariableExpr(), arg);
         }
+        clonedLeftVertex.setSourceLocation(edgePatternExpr.getLeftVertex().getSourceLocation());
+        clonedRightVertex.setSourceLocation(edgePatternExpr.getRightVertex().getSourceLocation());
 
         // Generate a cloned edge.
         EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
         Set<ElementLabel> clonedEdgeDescriptorLabels = new HashSet<>(edgeDescriptor.getEdgeLabels());
+        Expression clonedFilterExpr = null;
+        if (edgeDescriptor.getFilterExpr() != null) {
+            clonedFilterExpr = (Expression) edgeDescriptor.getFilterExpr().accept(this, arg);
+        }
         EdgeDescriptor clonedDescriptor = new EdgeDescriptor(edgeDescriptor.getEdgeDirection(),
-                edgeDescriptor.getPatternType(), clonedEdgeDescriptorLabels, clonedVariableExpr,
+                edgeDescriptor.getPatternType(), clonedEdgeDescriptorLabels, clonedFilterExpr, clonedVariableExpr,
                 edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops());
         EdgePatternExpr clonedEdge = new EdgePatternExpr(clonedLeftVertex, clonedRightVertex, clonedDescriptor);
-        clonedEdge.replaceInternalVertices(clonedInternalVertices);
+        clonedEdge.setInternalVertex(clonedInternalVertex);
+        clonedEdge.setSourceLocation(edgePatternExpr.getSourceLocation());
         return clonedEdge;
     }
 
@@ -167,8 +194,15 @@
         if (vertexPatternExpr.getVariableExpr() != null) {
             clonedVariableExpr = this.visit(vertexPatternExpr.getVariableExpr(), arg);
         }
-        Set<ElementLabel> clonedVertexLabels = new HashSet<>(vertexPatternExpr.getLabels());
-        return new VertexPatternExpr(clonedVariableExpr, clonedVertexLabels);
+        Expression clonedFilterExpr = null;
+        if (vertexPatternExpr.getFilterExpr() != null) {
+            clonedFilterExpr = (Expression) vertexPatternExpr.getFilterExpr().accept(this, arg);
+        }
+        Set<ElementLabel> clonedElementLabels = new HashSet<>(vertexPatternExpr.getLabels());
+        VertexPatternExpr clonedVertexExpr =
+                new VertexPatternExpr(clonedVariableExpr, clonedFilterExpr, clonedElementLabels);
+        clonedVertexExpr.setSourceLocation(vertexPatternExpr.getSourceLocation());
+        return clonedVertexExpr;
     }
 
     // We do not touch our GRAPH-CONSTRUCTOR here.
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixFunctionCallVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixFunctionCallVisitor.java
similarity index 96%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixFunctionCallVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixFunctionCallVisitor.java
index 399cb43..de34713 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixFunctionCallVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixFunctionCallVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import java.util.Map;
 
@@ -27,7 +27,7 @@
 import org.apache.asterix.graphix.function.GraphixFunctionMap;
 import org.apache.asterix.graphix.function.GraphixFunctionResolver;
 import org.apache.asterix.graphix.function.rewrite.IFunctionRewrite;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixLoweringVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixLoweringVisitor.java
new file mode 100644
index 0000000..088e698
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/GraphixLoweringVisitor.java
@@ -0,0 +1,530 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isEdgeFunction;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isVertexFunction;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.algebra.compiler.option.IGraphixCompilerOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateEdgeOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateVertexOption;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.lang.annotation.LoweringExemptAnnotation;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause.ClauseInputEnvironment;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause.ClauseOutputEnvironment;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.optype.MatchType;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.common.BranchLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.AliasLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.EnvironmentActionFactory;
+import org.apache.asterix.graphix.lang.rewrite.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.graphix.lang.rewrite.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.statement.Query;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+
+/**
+ * Rewrite a graph AST to utilize non-graph AST nodes (i.e. replace FROM-GRAPH-CLAUSEs with a LOWER-{LIST|BFS}-CLAUSE).
+ */
+public class GraphixLoweringVisitor extends AbstractGraphixQueryVisitor {
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
+    private final VariableRemapCloneVisitor variableRemapCloneVisitor;
+    private final ElementLookupTable elementLookupTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+    private SelectExpression topLevelSelectExpression;
+
+    // Our stack corresponds to which GRAPH-SELECT-BLOCK we are currently working with.
+    private final Map<IElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
+    private final Deque<LoweringEnvironment> environmentStack;
+    private final AliasLookupTable aliasLookupTable;
+    private final BranchLookupTable branchLookupTable;
+    private final EnvironmentActionFactory actionFactory;
+
+    public GraphixLoweringVisitor(GraphixRewritingContext graphixRewritingContext,
+            ElementLookupTable elementLookupTable, BranchLookupTable branchLookupTable) {
+        this.branchLookupTable = Objects.requireNonNull(branchLookupTable);
+        this.elementLookupTable = Objects.requireNonNull(elementLookupTable);
+        this.graphixRewritingContext = Objects.requireNonNull(graphixRewritingContext);
+        this.variableRemapCloneVisitor = new VariableRemapCloneVisitor(graphixRewritingContext);
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.aliasLookupTable = new AliasLookupTable();
+        this.environmentStack = new ArrayDeque<>();
+        this.analysisContextMap = new HashMap<>();
+
+        // All actions on our environment are supplied by the factory below.
+        this.actionFactory = new EnvironmentActionFactory(analysisContextMap, elementLookupTable, aliasLookupTable,
+                graphixRewritingContext);
+    }
+
+    @Override
+    public Expression visit(Query query, ILangExpression arg) throws CompilationException {
+        boolean isTopLevelQuery = query.isTopLevel();
+        boolean isSelectExpr = query.getBody().getKind() == Expression.Kind.SELECT_EXPRESSION;
+        if (isSelectExpr && (isTopLevelQuery || topLevelSelectExpression == null)) {
+            topLevelSelectExpression = (SelectExpression) query.getBody();
+        }
+        return super.visit(query, arg);
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        SelectExpression selectExpression = (SelectExpression) arg;
+        if (selectBlock.hasFromClause() && selectBlock.getFromClause() instanceof FromGraphClause) {
+            FromGraphClause fromGraphClause = (FromGraphClause) selectBlock.getFromClause();
+            MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+            GraphIdentifier graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
+            SourceLocation sourceLocation = fromGraphClause.getSourceLocation();
+
+            // Initialize a new lowering environment.
+            LoweringEnvironment newEnvironment =
+                    new LoweringEnvironment(graphixRewritingContext, graphIdentifier, sourceLocation);
+            actionFactory.reset(graphIdentifier);
+
+            // We will remove the FROM-GRAPH node and replace this with a FROM node on the child visit.
+            environmentStack.addLast(newEnvironment);
+            super.visit(selectBlock, arg);
+            environmentStack.removeLast();
+
+            // See if we need to perform a pass for schema enrichment. By default, we decorate "as-needed".
+            String schemaDecorateVertexKey = SchemaDecorateVertexOption.OPTION_KEY_NAME;
+            String schemaDecorateEdgeKey = SchemaDecorateEdgeOption.OPTION_KEY_NAME;
+            IGraphixCompilerOption vertexModeOption = graphixRewritingContext.getSetting(schemaDecorateVertexKey);
+            IGraphixCompilerOption edgeModeOption = graphixRewritingContext.getSetting(schemaDecorateEdgeKey);
+            SchemaDecorateVertexOption vertexMode = (SchemaDecorateVertexOption) vertexModeOption;
+            SchemaDecorateEdgeOption edgeMode = (SchemaDecorateEdgeOption) edgeModeOption;
+
+            // See if there are any Graphix functions used in our query.
+            Set<FunctionIdentifier> graphixFunctionSet = new LinkedHashSet<>();
+            topLevelSelectExpression.accept(new AbstractGraphixQueryVisitor() {
+                @Override
+                public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+                    FunctionSignature functionSignature = callExpr.getFunctionSignature();
+                    if (functionSignature.getDataverseName().equals(GraphixFunctionIdentifiers.GRAPHIX_DV)) {
+                        FunctionIdentifier functionID = functionSignature.createFunctionIdentifier();
+                        if ((vertexMode == SchemaDecorateVertexOption.NEVER && isVertexFunction(functionID))
+                                || (edgeMode == SchemaDecorateEdgeOption.NEVER && isEdgeFunction(functionID))) {
+                            throw new CompilationException(ErrorCode.COMPILATION_ERROR, callExpr.getSourceLocation(),
+                                    "Schema-decorate mode has been set to 'NEVER', but schema-decoration is required "
+                                            + "to realize the function" + functionSignature + "!");
+                        }
+                        graphixFunctionSet.add(functionID);
+                    }
+                    return super.visit(callExpr, arg);
+                }
+            }, null);
+
+            // Perform a pass for schema enrichment, if needed.
+            boolean isVertexModeAlways = vertexMode == SchemaDecorateVertexOption.ALWAYS;
+            boolean isEdgeModeAlways = edgeMode == SchemaDecorateEdgeOption.ALWAYS;
+            if (!graphixFunctionSet.isEmpty() || isVertexModeAlways || isEdgeModeAlways) {
+                SchemaEnrichmentVisitor schemaEnrichmentVisitor = new SchemaEnrichmentVisitor(vertexMode, edgeMode,
+                        elementLookupTable, branchLookupTable, graphIdentifier, selectBlock, graphixFunctionSet);
+                selectExpression.accept(schemaEnrichmentVisitor, null);
+                if (selectExpression.hasOrderby()) {
+                    selectExpression.getOrderbyClause().accept(schemaEnrichmentVisitor, null);
+                }
+                if (selectExpression.hasLimit()) {
+                    selectExpression.getLimitClause().accept(schemaEnrichmentVisitor, null);
+                }
+            }
+
+        } else {
+            super.visit(selectBlock, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        // Perform an analysis pass over each element body. We need to determine what we can and can't inline.
+        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
+            ElementBodyAnalysisVisitor elementBodyAnalysisVisitor = new ElementBodyAnalysisVisitor();
+            IElementIdentifier elementIdentifier = graphElementDeclaration.getIdentifier();
+            graphElementDeclaration.getNormalizedBody().accept(elementBodyAnalysisVisitor, null);
+            analysisContextMap.put(elementIdentifier, elementBodyAnalysisVisitor.getElementBodyAnalysisContext());
+        }
+        LoweringEnvironment workingEnvironment = environmentStack.getLast();
+
+        // TODO (GLENN): Perform smarter analysis to determine when a vertex / edge is inlineable w/ LEFT-MATCH.
+        Stream<MatchClause> matchClauseStream = fromGraphClause.getMatchClauses().stream();
+        workingEnvironment.setInlineLegal(matchClauseStream.noneMatch(c -> c.getMatchType() == MatchType.LEFTOUTER));
+
+        // Lower our MATCH-CLAUSEs. We should be working with canonical-ized patterns.
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            if (matchClause.getMatchType() == MatchType.LEFTOUTER) {
+                workingEnvironment.beginLeftMatch();
+            }
+            for (PathPatternExpr pathPatternExpr : matchClause.getPathExpressions()) {
+                for (EdgePatternExpr edgePatternExpr : pathPatternExpr.getEdgeExpressions()) {
+                    edgePatternExpr.accept(this, fromGraphClause);
+                }
+                for (VertexPatternExpr vertexPatternExpr : pathPatternExpr.getVertexExpressions()) {
+                    VariableExpr vertexVariableExpr = vertexPatternExpr.getVariableExpr();
+                    if (aliasLookupTable.getIterationAlias(vertexVariableExpr) != null) {
+                        continue;
+                    }
+                    workingEnvironment.acceptAction(actionFactory.buildDanglingVertexAction(vertexPatternExpr));
+                }
+                workingEnvironment.acceptAction(actionFactory.buildPathPatternAction(pathPatternExpr));
+            }
+            if (matchClause.getMatchType() == MatchType.LEFTOUTER) {
+                workingEnvironment.endLeftMatch();
+            }
+        }
+        workingEnvironment.acceptAction(actionFactory.buildMatchSemanticAction(fromGraphClause));
+
+        // Finalize our lowering by moving our lower list to our environment.
+        workingEnvironment.endLowering(fromGraphClause);
+
+        // Add our correlate clauses, if any, to our tail FROM-TERM.
+        if (!fromGraphClause.getCorrelateClauses().isEmpty()) {
+            LowerListClause lowerClause = (LowerListClause) fromGraphClause.getLowerClause();
+            ClauseCollection clauseCollection = lowerClause.getClauseCollection();
+            fromGraphClause.getCorrelateClauses().forEach(clauseCollection::addUserDefinedCorrelateClause);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
+            lowerCanonicalExpandedEdge(edgePatternExpr, environmentStack.getLast());
+
+        } else { // edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
+            lowerCanonicalExpandedPath(edgePatternExpr, environmentStack.getLast());
+        }
+        return edgePatternExpr;
+    }
+
+    private void lowerCanonicalExpandedEdge(EdgePatternExpr edgePatternExpr, LoweringEnvironment environment)
+            throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+
+        // We should be working with a canonical edge.
+        GraphIdentifier graphIdentifier = environment.getGraphIdentifier();
+        List<EdgeIdentifier> edgeElementIDs = edgePatternExpr.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Encountered non-fixed-point edge pattern!");
+        }
+        EdgeIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeBodyAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        DataverseName edgeDataverseName = edgeBodyAnalysisContext.getDataverseName();
+        String edgeDatasetName = edgeBodyAnalysisContext.getDatasetName();
+
+        // Determine our source and destination vertices.
+        VertexPatternExpr sourceVertex, destVertex;
+        if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+            sourceVertex = edgePatternExpr.getLeftVertex();
+            destVertex = edgePatternExpr.getRightVertex();
+
+        } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
+            sourceVertex = edgePatternExpr.getRightVertex();
+            destVertex = edgePatternExpr.getLeftVertex();
+        }
+
+        // Collect information about our source vertex.
+        VertexIdentifier sourceIdentifier = sourceVertex.generateIdentifiers(graphIdentifier).get(0);
+        ElementBodyAnalysisContext sourceBodyAnalysisContext = analysisContextMap.get(sourceIdentifier);
+        VariableExpr sourceVertexVariable = sourceVertex.getVariableExpr();
+        List<List<String>> sourceVertexKey = elementLookupTable.getVertexKey(sourceIdentifier);
+        Function<EdgeIdentifier, List<List<String>>> sourceKey = elementLookupTable::getEdgeSourceKey;
+        boolean isSourceInline = sourceBodyAnalysisContext.isExpressionInline() && environment.isInlineLegal();
+        boolean isSourceIntroduced = aliasLookupTable.getIterationAlias(sourceVertexVariable) != null;
+
+        // ...and our destination vertex...
+        VertexIdentifier destIdentifier = destVertex.generateIdentifiers(graphIdentifier).get(0);
+        ElementBodyAnalysisContext destBodyAnalysisContext = analysisContextMap.get(destIdentifier);
+        VariableExpr destVertexVariable = destVertex.getVariableExpr();
+        List<List<String>> destVertexKey = elementLookupTable.getVertexKey(destIdentifier);
+        Function<EdgeIdentifier, List<List<String>>> destKey = elementLookupTable::getEdgeDestKey;
+        boolean isDestInline = destBodyAnalysisContext.isExpressionInline() && environment.isInlineLegal();
+        boolean isDestIntroduced = aliasLookupTable.getIterationAlias(destVertexVariable) != null;
+
+        // ...and our edge.
+        List<List<String>> sourceEdgeKey = elementLookupTable.getEdgeSourceKey(edgeIdentifier);
+        List<List<String>> destEdgeKey = elementLookupTable.getEdgeDestKey(edgeIdentifier);
+        String sourceBodyDatasetName = sourceBodyAnalysisContext.getDatasetName();
+        String destBodyDatasetName = destBodyAnalysisContext.getDatasetName();
+        boolean isEdgeInline = edgeBodyAnalysisContext.isExpressionInline() && environment.isInlineLegal();
+        boolean isSourceFolded = isSourceInline && sourceBodyDatasetName.equals(edgeDatasetName)
+                && sourceBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
+                && sourceVertexKey.equals(sourceEdgeKey);
+        boolean isDestFolded = isDestInline && destBodyDatasetName.equals(edgeDatasetName)
+                && destBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
+                && destVertexKey.equals(destEdgeKey);
+
+        // Condition our strategy on which vertices are currently introduced.
+        if (isEdgeInline && isSourceFolded) {
+            if (!isSourceIntroduced) {
+                environment.acceptAction(actionFactory.buildDanglingVertexAction(sourceVertex));
+            }
+            environment.acceptAction(actionFactory.buildFoldedEdgeAction(sourceVertex, edgePatternExpr));
+            environment.acceptAction(
+                    !isDestIntroduced ? actionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey)
+                            : actionFactory.buildRawJoinVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isEdgeInline && isDestFolded) {
+            if (!isDestIntroduced) {
+                environment.acceptAction(actionFactory.buildDanglingVertexAction(destVertex));
+            }
+            environment.acceptAction(actionFactory.buildFoldedEdgeAction(destVertex, edgePatternExpr));
+            environment.acceptAction(
+                    !isSourceIntroduced ? actionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey)
+                            : actionFactory.buildRawJoinVertexAction(sourceVertex, edgePatternExpr, sourceKey));
+
+        } else if (isSourceIntroduced && isDestIntroduced) {
+            environment.acceptAction(actionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
+            environment.acceptAction(actionFactory.buildRawJoinVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isSourceIntroduced) { // !isDestIntroduced
+            environment.acceptAction(actionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
+            environment.acceptAction(actionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isDestIntroduced) { // !isSourceIntroduced
+            environment.acceptAction(actionFactory.buildNonFoldedEdgeAction(destVertex, edgePatternExpr, destKey));
+            environment.acceptAction(actionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey));
+
+        } else { // !isSourceIntroduced && !isDestIntroduced
+            // When nothing is introduced, start off from LEFT to RIGHT instead of considering our source and dest.
+            VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+            VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+            Function<EdgeIdentifier, List<List<String>>> leftKey;
+            Function<EdgeIdentifier, List<List<String>>> rightKey;
+            if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+                leftKey = sourceKey;
+                rightKey = destKey;
+
+            } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
+                leftKey = destKey;
+                rightKey = sourceKey;
+            }
+            environment.acceptAction(actionFactory.buildDanglingVertexAction(leftVertex));
+            environment.acceptAction(actionFactory.buildNonFoldedEdgeAction(leftVertex, edgePatternExpr, leftKey));
+            environment.acceptAction(actionFactory.buildBoundVertexAction(rightVertex, edgePatternExpr, rightKey));
+        }
+    }
+
+    private void lowerCanonicalExpandedPath(EdgePatternExpr edgePatternExpr, LoweringEnvironment environment)
+            throws CompilationException {
+        // Determine the starting vertex of our path.
+        VariableExpr leftVertexVariable = edgePatternExpr.getLeftVertex().getVariableExpr();
+        VariableExpr rightVertexVariable = edgePatternExpr.getRightVertex().getVariableExpr();
+        boolean isLeftVertexIntroduced = aliasLookupTable.getIterationAlias(leftVertexVariable) != null;
+        boolean isRightVertexIntroduced = aliasLookupTable.getIterationAlias(rightVertexVariable) != null;
+        boolean isJoiningLeftToRight = isLeftVertexIntroduced || !isRightVertexIntroduced;
+        VertexPatternExpr inputVertex, outputVertex;
+        if (isJoiningLeftToRight) {
+            inputVertex = edgePatternExpr.getLeftVertex();
+            outputVertex = edgePatternExpr.getRightVertex();
+
+        } else {
+            inputVertex = edgePatternExpr.getRightVertex();
+            outputVertex = edgePatternExpr.getLeftVertex();
+        }
+        VariableExpr inputVertexVariable = inputVertex.getVariableExpr();
+        VariableExpr outputVertexVariable = outputVertex.getVariableExpr();
+
+        // If we need to, introduce our left vertex (only occurs if nothing has been introduced).
+        if (!isLeftVertexIntroduced && !isRightVertexIntroduced) {
+            environment.acceptAction(actionFactory.buildDanglingVertexAction(edgePatternExpr.getLeftVertex()));
+        }
+
+        // Our input vertex must be introduced eagerly to be given to our graph clause as input.
+        VariableExpr inputClauseVariable = graphixRewritingContext.getGraphixVariableCopy(inputVertexVariable);
+        environment.acceptTransformer(lowerList -> {
+            // Find the representative vertex associated with our input.
+            Expression representativeVertexExpr = null;
+            for (LetClause vertexBinding : lowerList.getRepresentativeVertexBindings()) {
+                if (vertexBinding.getVarExpr().equals(inputVertex.getVariableExpr())) {
+                    representativeVertexExpr = vertexBinding.getBindingExpr();
+                    break;
+                }
+            }
+            if (representativeVertexExpr == null) {
+                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Input vertex not found!");
+            }
+
+            // Once all variables of our representative LET-CLAUSE have been introduced, introduce our input binding.
+            Set<VariableExpr> usedVariables = SqlppVariableUtil.getFreeVariables(representativeVertexExpr);
+            ListIterator<AbstractClause> nonRepresentativeClauseIterator =
+                    lowerList.getNonRepresentativeClauses().listIterator();
+            while (nonRepresentativeClauseIterator.hasNext()) {
+                AbstractClause workingClause = nonRepresentativeClauseIterator.next();
+                List<VariableExpr> bindingVariables = SqlppVariableUtil.getBindingVariables(workingClause);
+                bindingVariables.forEach(usedVariables::remove);
+                if (usedVariables.isEmpty()) {
+                    VariableExpr inputClauseVariableCopy = graphixDeepCopyVisitor.visit(inputClauseVariable, null);
+                    LetClause clauseInputBinding = new LetClause(inputClauseVariableCopy, representativeVertexExpr);
+                    nonRepresentativeClauseIterator.add(clauseInputBinding);
+                    lowerList.addEagerVertexBinding(inputVertex.getVariableExpr(), clauseInputBinding);
+                    break;
+                }
+            }
+        });
+
+        // Lower each of our branches.
+        environment.beginBranches();
+        Map<ElementLabel, VariableExpr> labelInputVariableMap = new HashMap<>();
+        Map<ElementLabel, VariableExpr> labelOutputVariableMap = new HashMap<>();
+        for (EdgePatternExpr branch : branchLookupTable.getBranches(edgePatternExpr)) {
+            VertexPatternExpr inputBranchVertex, outputBranchVertex;
+            if (isJoiningLeftToRight) {
+                inputBranchVertex = branch.getLeftVertex();
+                outputBranchVertex = branch.getRightVertex();
+
+            } else {
+                inputBranchVertex = branch.getRightVertex();
+                outputBranchVertex = branch.getLeftVertex();
+            }
+
+            // Introduce the alias-- but do not lower our source vertex.
+            environment.beginTempLowerList();
+            environment.acceptAction(actionFactory.buildDanglingVertexAction(inputBranchVertex));
+            environment.endTempLowerList();
+            synchronizeVariables(environment, inputBranchVertex, labelInputVariableMap);
+            inputBranchVertex.addHint(LoweringExemptAnnotation.INSTANCE);
+
+            // Lower our branch, having introduced our source vertex.
+            lowerCanonicalExpandedEdge(branch, environment);
+            synchronizeVariables(environment, outputBranchVertex, labelOutputVariableMap);
+            environment.flushBranch(branch, isJoiningLeftToRight);
+        }
+
+        // Our output vertex will be aliased by the following:
+        VariableExpr outputVertexIterationAlias = graphixRewritingContext.getGraphixVariableCopy(outputVertexVariable);
+        VariableExpr outputVertexJoinAlias = graphixRewritingContext.getGraphixVariableCopy(outputVertexVariable);
+        aliasLookupTable.addIterationAlias(outputVertexVariable, outputVertexIterationAlias);
+        aliasLookupTable.addJoinAlias(outputVertexVariable, outputVertexJoinAlias);
+
+        // Introduce a SWITCH node, finalizing our path lowering.
+        VariableExpr pathVariable = edgePatternExpr.getEdgeDescriptor().getVariableExpr();
+        VariableExpr outputClauseVariable = graphixRewritingContext.getGraphixVariableCopy(outputVertexVariable);
+        ClauseOutputEnvironment outputEnvironment = new ClauseOutputEnvironment(outputClauseVariable,
+                aliasLookupTable.getIterationAlias(outputVertexVariable),
+                aliasLookupTable.getJoinAlias(outputVertexVariable), pathVariable,
+                outputVertex.getLabels().iterator().next());
+        ClauseInputEnvironment inputEnvironment = new ClauseInputEnvironment(
+                graphixDeepCopyVisitor.visit(inputClauseVariable, null), inputVertex.getLabels().iterator().next());
+        environment.endBranches(outputEnvironment, inputEnvironment, labelInputVariableMap, labelOutputVariableMap,
+                aliasLookupTable, edgePatternExpr.getSourceLocation());
+
+        // Expose our output vertex and path to the remainder of our query.
+        environment.acceptTransformer(lowerList -> {
+            VariableExpr iterationVariableCopy1 = graphixDeepCopyVisitor.visit(outputVertexIterationAlias, null);
+            VariableExpr iterationVariableCopy2 = graphixDeepCopyVisitor.visit(outputVertexIterationAlias, null);
+            VariableExpr joinVariableCopy = graphixDeepCopyVisitor.visit(outputVertexJoinAlias, null);
+            Expression iterationVariableAccess = outputEnvironment.buildIterationVariableAccess();
+            Expression joinVariableAccess = outputEnvironment.buildJoinVariableAccess();
+            lowerList.addNonRepresentativeClause(new LetClause(iterationVariableCopy1, iterationVariableAccess));
+            lowerList.addNonRepresentativeClause(new LetClause(joinVariableCopy, joinVariableAccess));
+            lowerList.addVertexBinding(outputVertexVariable, iterationVariableCopy2);
+            lowerList.addPathBinding(pathVariable, outputEnvironment.buildPathVariableAccess());
+        });
+    }
+
+    private void synchronizeVariables(LoweringEnvironment workingEnvironment, VertexPatternExpr branchVertex,
+            Map<ElementLabel, VariableExpr> labelVariableMap) throws CompilationException {
+        VariableExpr vertexVariable = branchVertex.getVariableExpr();
+        VariableExpr iterationAlias = aliasLookupTable.getIterationAlias(vertexVariable);
+        VariableExpr joinAlias = aliasLookupTable.getJoinAlias(vertexVariable);
+        ElementLabel elementLabel = branchVertex.getLabels().iterator().next();
+        if (labelVariableMap.containsKey(elementLabel)) {
+            VariableExpr targetVertexVariable = labelVariableMap.get(elementLabel);
+            VariableExpr targetIterationAlias = aliasLookupTable.getIterationAlias(targetVertexVariable);
+            VariableExpr targetJoinAlias = aliasLookupTable.getJoinAlias(targetVertexVariable);
+            variableRemapCloneVisitor.resetSubstitutions();
+            variableRemapCloneVisitor.addSubstitution(iterationAlias, targetIterationAlias);
+            variableRemapCloneVisitor.addSubstitution(joinAlias, targetJoinAlias);
+            variableRemapCloneVisitor.addSubstitution(vertexVariable, targetVertexVariable);
+
+            workingEnvironment.acceptTransformer(lowerList -> {
+                // Transform our non-representative clauses.
+                ListIterator<AbstractClause> nonRepresentativeIterator =
+                        lowerList.getNonRepresentativeClauses().listIterator();
+                while (nonRepresentativeIterator.hasNext()) {
+                    AbstractClause workingClause = nonRepresentativeIterator.next();
+                    nonRepresentativeIterator.set((AbstractClause) variableRemapCloneVisitor.substitute(workingClause));
+                }
+
+                // Transform our vertex bindings.
+                ListIterator<LetClause> vertexBindingIterator =
+                        lowerList.getRepresentativeVertexBindings().listIterator();
+                while (vertexBindingIterator.hasNext()) {
+                    LetClause vertexBinding = vertexBindingIterator.next();
+                    vertexBindingIterator.set((LetClause) variableRemapCloneVisitor.substitute(vertexBinding));
+                }
+
+                // Transform our edge bindings.
+                ListIterator<LetClause> edgeBindingIterator = lowerList.getRepresentativeEdgeBindings().listIterator();
+                while (edgeBindingIterator.hasNext()) {
+                    LetClause edgeBinding = edgeBindingIterator.next();
+                    edgeBindingIterator.set((LetClause) variableRemapCloneVisitor.substitute(edgeBinding));
+                }
+            });
+            branchVertex.setVariableExpr(targetVertexVariable);
+
+        } else {
+            labelVariableMap.put(elementLabel, vertexVariable);
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PatternGraphGroupVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PatternGraphGroupVisitor.java
new file mode 100644
index 0000000..98842d1
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PatternGraphGroupVisitor.java
@@ -0,0 +1,87 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.struct.PatternGroup;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+
+/**
+ * Aggregate all query patterns, grouped by the graph being queried.
+ *
+ * @see PatternGroup
+ */
+public class PatternGraphGroupVisitor extends AbstractGraphixQueryVisitor {
+    private final Map<GraphIdentifier, PatternGroup> patternGroupMap;
+    private final Deque<GraphIdentifier> graphIdentifierStack;
+    protected final MetadataProvider metadataProvider;
+
+    public PatternGraphGroupVisitor(Map<GraphIdentifier, PatternGroup> patternGroupMap,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
+        this.graphIdentifierStack = new ArrayDeque<>();
+        this.patternGroupMap = patternGroupMap;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        // Collect the vertices and edges in this FROM-GRAPH-CLAUSE.
+        graphIdentifierStack.push(fromGraphClause.getGraphIdentifier(metadataProvider));
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            matchClause.accept(this, arg);
+        }
+        for (AbstractBinaryCorrelateClause correlateClause : fromGraphClause.getCorrelateClauses()) {
+            correlateClause.accept(this, arg);
+        }
+        graphIdentifierStack.pop();
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
+        patternGroupMap.putIfAbsent(graphIdentifierStack.peek(), new PatternGroup());
+        patternGroupMap.get(graphIdentifierStack.peek()).getVertexPatternSet().add(vertexPatternExpr);
+        return super.visit(vertexPatternExpr, arg);
+    }
+
+    // We do not visit our internal vertices here.
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        patternGroupMap.putIfAbsent(graphIdentifierStack.peek(), new PatternGroup());
+        patternGroupMap.get(graphIdentifierStack.peek()).getEdgePatternSet().add(edgePatternExpr);
+        if (edgePatternExpr.getEdgeDescriptor().getFilterExpr() != null) {
+            edgePatternExpr.getEdgeDescriptor().getFilterExpr().accept(this, arg);
+        }
+        return edgePatternExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PopulateUnknownsVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PopulateUnknownsVisitor.java
new file mode 100644
index 0000000..37f341f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PopulateUnknownsVisitor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * A pre-Graphix transformation pass to populate a number of unknowns in our Graphix AST.
+ * <ol>
+ *  <li>Populate all unknown graph elements (vertices and edges).</li>
+ *  <li>Populate all unknown column names in SELECT-CLAUSEs.</li>
+ *  <li>Populate all unknown GROUP-BY keys.</li>
+ *  <li>Fill in all GROUP-BY fields.</li>
+ * </ol>
+ */
+public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
+    private final GenerateColumnNameVisitor generateColumnNameVisitor;
+    private final Supplier<VariableExpr> newVariableSupplier;
+
+    public PopulateUnknownsVisitor(GraphixRewritingContext graphixRewritingContext) {
+        generateColumnNameVisitor = new GenerateColumnNameVisitor(graphixRewritingContext);
+        newVariableSupplier = () -> new VariableExpr(graphixRewritingContext.newVariable());
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        selectExpression.accept(generateColumnNameVisitor, arg);
+        return super.visit(selectExpression, arg);
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        super.visit(selectBlock, arg);
+
+        if (selectBlock.hasGroupbyClause()) {
+            // Collect all variables that should belong in the GROUP-BY field list.
+            Set<VariableExpr> userLiveVariables = new HashSet<>();
+            if (selectBlock.hasFromClause() && selectBlock.getFromClause() instanceof FromGraphClause) {
+                FromGraphClause fromGraphClause = (FromGraphClause) selectBlock.getFromClause();
+                for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+                    for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                        if (pathExpression.getVariableExpr() != null) {
+                            userLiveVariables.add(pathExpression.getVariableExpr());
+                        }
+                        for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                            userLiveVariables.add(vertexExpression.getVariableExpr());
+                        }
+                        for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                            userLiveVariables.add(edgeExpression.getEdgeDescriptor().getVariableExpr());
+                        }
+                    }
+                }
+                if (!fromGraphClause.getCorrelateClauses().isEmpty()) {
+                    List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
+                    for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
+                        userLiveVariables.add(correlateClause.getRightVariable());
+                    }
+                }
+
+            } else if (selectBlock.hasFromClause()) {
+                FromClause fromClause = selectBlock.getFromClause();
+                for (FromTerm fromTerm : fromClause.getFromTerms()) {
+                    userLiveVariables.add(fromTerm.getLeftVariable());
+                    for (AbstractBinaryCorrelateClause correlateClause : fromTerm.getCorrelateClauses()) {
+                        userLiveVariables.add(correlateClause.getRightVariable());
+                    }
+                }
+            }
+            if (selectBlock.hasLetWhereClauses()) {
+                for (AbstractClause abstractClause : selectBlock.getLetWhereList()) {
+                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) abstractClause;
+                        userLiveVariables.add(letClause.getVarExpr());
+                    }
+                }
+            }
+
+            // Add the live variables to our GROUP-BY field list.
+            List<Pair<Expression, Identifier>> newGroupFieldList = new ArrayList<>();
+            for (VariableExpr userLiveVariable : userLiveVariables) {
+                String variableName = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getVar().getValue());
+                newGroupFieldList.add(new Pair<>(userLiveVariable, new Identifier(variableName)));
+            }
+            selectBlock.getGroupbyClause().setGroupFieldList(newGroupFieldList);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
+        if (vertexExpression.getVariableExpr() == null) {
+            vertexExpression.setVariableExpr(newVariableSupplier.get());
+        }
+        return super.visit(vertexExpression, arg);
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
+        if (edgeDescriptor.getVariableExpr() == null) {
+            edgeDescriptor.setVariableExpr(newVariableSupplier.get());
+        }
+        return super.visit(edgeExpression, arg);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostCanonicalExpansionVisitor.java
similarity index 67%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostCanonicalExpansionVisitor.java
index bf6cb97..1f0d85b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostCanonicalExpansionVisitor.java
@@ -16,15 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
-import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
@@ -50,42 +50,48 @@
 
 /**
  * Rewrite a SELECT-EXPR and its SET-OP inputs to perform the following:
- * 1. Expose all user-defined variables from each SET-OP by modifying their SELECT-CLAUSE.
- * 2. Qualify the source SELECT-CLAUSE (before expansion) with the nesting variable.
- * 3. Qualify our output modifiers (ORDER-BY, LIMIT) with the nesting variable.
- * 4. Qualify our GROUP-BY / GROUP-AS (the grouping list) / HAVING / LET (after GROUP-BY) clauses with the nesting
- * variable.
+ * <ol>
+ *  <li>Expose all user-defined variables from each SET-OP by modifying their SELECT-CLAUSE.</li>
+ *  <li>Qualify the source SELECT-CLAUSE (before expansion) with the nesting variable.</li>
+ *  <li>Qualify our output modifiers (ORDER-BY, LIMIT) with the nesting variable.</li>
+ *  <li>Qualify our GROUP-BY / GROUP-AS (the grouping list) / HAVING / LET (after GROUP-BY) clauses with the nesting
+ *  variable.</li>
+ * </ol>
  */
-public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
+public class PostCanonicalExpansionVisitor extends AbstractGraphixQueryVisitor {
+    private final GraphixDeepCopyVisitor deepCopyVisitor;
     private final GraphixRewritingContext graphixRewritingContext;
     private final QualifyingVisitor qualifyingVisitor;
 
     // We require the following from our canonicalization pass.
-    private final Set<SetOperationInput> generatedSetOpInputs;
-    private final GraphSelectBlock selectBlockExpansionSource;
-    private final List<VarIdentifier> userLiveVariables;
+    private final Collection<SelectBlock> generatedSelectBlocks;
+    private final Collection<VariableExpr> sourceSelectLiveVariables;
+    private final SelectBlock selectBlockExpansionSource;
 
-    public PostCanonicalizationVisitor(GraphixRewritingContext graphixRewritingContext,
-            GraphSelectBlock selectBlockExpansionSource, Set<SetOperationInput> generatedSetOpInputs,
-            List<VarIdentifier> userLiveVariables) {
+    public PostCanonicalExpansionVisitor(GraphixRewritingContext graphixRewritingContext,
+            SelectBlock selectBlockExpansionSource, Collection<SelectBlock> generatedSelectBlocks,
+            Collection<VariableExpr> sourceSelectLiveVariables) {
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
         this.graphixRewritingContext = graphixRewritingContext;
         this.selectBlockExpansionSource = selectBlockExpansionSource;
-        this.generatedSetOpInputs = generatedSetOpInputs;
-        this.userLiveVariables = userLiveVariables;
+        this.generatedSelectBlocks = generatedSelectBlocks;
+        this.sourceSelectLiveVariables = sourceSelectLiveVariables;
         this.qualifyingVisitor = new QualifyingVisitor();
     }
 
     @Override
     public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        VariableExpr iterationVariableExpr = new VariableExpr(graphixRewritingContext.getNewGraphixVariable());
+        VariableExpr iterationVariable = graphixRewritingContext.getGraphixVariableCopy("_Containing");
 
         // Modify the involved SELECT-CLAUSEs to output our user-live variables and remove any GROUP-BY clauses.
         selectExpression.getSelectSetOperation().accept(this, arg);
-        FromTerm fromTerm = new FromTerm(selectExpression, iterationVariableExpr, null, null);
+        FromTerm fromTerm = new FromTerm(selectExpression, iterationVariable, null, null);
         FromClause fromClause = new FromClause(List.of(fromTerm));
+        fromTerm.setSourceLocation(selectExpression.getSourceLocation());
+        fromClause.setSourceLocation(selectExpression.getSourceLocation());
 
         // Qualify the SELECT-CLAUSE given to us by our caller.
-        qualifyingVisitor.qualifyingVar = iterationVariableExpr.getVar();
+        qualifyingVisitor.qualifyingVar = deepCopyVisitor.visit(iterationVariable, null);
         SelectClause selectClause = selectBlockExpansionSource.getSelectClause();
         selectClause.accept(qualifyingVisitor, null);
 
@@ -117,6 +123,8 @@
                 Expression newExpression = expressionIdentifierPair.first.accept(qualifyingVisitor, null);
                 newGroupFieldList.add(new Pair<>(newExpression, expressionIdentifierPair.second));
             }
+            VariableExpr iterationVariableCopy = deepCopyVisitor.visit(iterationVariable, null);
+            newGroupFieldList.add(new Pair<>(iterationVariableCopy, iterationVariable.getVar()));
             groupbyClause.setGroupFieldList(newGroupFieldList);
         }
         if (selectBlockExpansionSource.hasLetHavingClausesAfterGroupby()) {
@@ -128,21 +136,26 @@
         // Finalize our post-canonicalization: attach our SELECT-CLAUSE, GROUP-BY, output modifiers...
         SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, null, groupbyClause, null);
         selectBlock.getLetHavingListAfterGroupby().addAll(letHavingClausesAfterGby);
+        selectBlock.setSourceLocation(selectBlockExpansionSource.getSourceLocation());
         SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
         SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
-        return new SelectExpression(null, selectSetOperation, orderByClause, limitClause, isSubquery);
+        selectSetOperation.setSourceLocation(selectBlockExpansionSource.getSourceLocation());
+        SelectExpression newSelectExpression =
+                new SelectExpression(null, selectSetOperation, orderByClause, limitClause, isSubquery);
+        newSelectExpression.setSourceLocation(selectExpression.getSourceLocation());
+        return newSelectExpression;
     }
 
     @Override
     public Expression visit(SelectSetOperation selectSetOperation, ILangExpression arg) throws CompilationException {
         // Only visit SET-OP-INPUTs if they were involved in our canonicalization.
         SetOperationInput leftInput = selectSetOperation.getLeftInput();
-        if (generatedSetOpInputs.contains(leftInput)) {
+        if (leftInput.selectBlock() && generatedSelectBlocks.contains(leftInput.getSelectBlock())) {
             leftInput.getSelectBlock().accept(this, arg);
         }
         for (SetOperationRight setOperationRight : selectSetOperation.getRightInputs()) {
             SetOperationInput rightInput = setOperationRight.getSetOperationRightInput();
-            if (generatedSetOpInputs.contains(rightInput)) {
+            if (rightInput.selectBlock() && generatedSelectBlocks.contains(rightInput.getSelectBlock())) {
                 rightInput.getSelectBlock().accept(this, arg);
             }
         }
@@ -150,11 +163,6 @@
     }
 
     @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        return visit((SelectBlock) graphSelectBlock, arg);
-    }
-
-    @Override
     public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
         if (selectBlock.hasGroupbyClause()) {
             selectBlock.setGroupbyClause(null);
@@ -170,10 +178,10 @@
     public Expression visit(SelectClause selectClause, ILangExpression arg) throws CompilationException {
         // We are going to throw away this SELECT-CLAUSE and return all user-live variables instead.
         List<Projection> newProjectionList = new ArrayList<>();
-        for (VarIdentifier userLiveVariable : userLiveVariables) {
-            String name = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getValue());
-            VariableExpr newVariableExpr = new VariableExpr(userLiveVariable);
-            newProjectionList.add(new Projection(Projection.Kind.NAMED_EXPR, newVariableExpr, name));
+        for (VariableExpr userLiveVariable : sourceSelectLiveVariables) {
+            String name = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getVar().getValue());
+            VariableExpr userLiveVariableCopy = deepCopyVisitor.visit(userLiveVariable, null);
+            newProjectionList.add(new Projection(Projection.Kind.NAMED_EXPR, userLiveVariableCopy, name));
         }
         selectClause.setSelectElement(null);
         selectClause.setSelectRegular(new SelectRegular(newProjectionList));
@@ -181,13 +189,16 @@
     }
 
     private class QualifyingVisitor extends AbstractGraphixQueryVisitor {
-        private VarIdentifier qualifyingVar;
+        private VariableExpr qualifyingVar;
 
         @Override
         public Expression visit(VariableExpr variableExpr, ILangExpression arg) throws CompilationException {
-            if (userLiveVariables.contains(variableExpr.getVar())) {
+            if (sourceSelectLiveVariables.contains(variableExpr)) {
                 VarIdentifier fieldAccessVar = SqlppVariableUtil.toUserDefinedVariableName(variableExpr.getVar());
-                return new FieldAccessor(new VariableExpr(qualifyingVar), fieldAccessVar);
+                VariableExpr qualifyingVariableCopy = deepCopyVisitor.visit(qualifyingVar, null);
+                FieldAccessor fieldAccessor = new FieldAccessor(qualifyingVariableCopy, fieldAccessVar);
+                fieldAccessor.setSourceLocation(variableExpr.getSourceLocation());
+                return fieldAccessor;
             }
             return super.visit(variableExpr, arg);
         }
@@ -195,17 +206,22 @@
         @Override
         public Expression visit(FieldAccessor fieldAccessor, ILangExpression arg) throws CompilationException {
             Expression fieldAccessorExpr = fieldAccessor.getExpr();
+            Identifier fieldAccessIdent = fieldAccessor.getIdent();
             if (fieldAccessorExpr.getKind() == Expression.Kind.FIELD_ACCESSOR_EXPRESSION) {
                 FieldAccessor innerFieldAccessExpr = (FieldAccessor) fieldAccessorExpr.accept(this, arg);
-                return new FieldAccessor(innerFieldAccessExpr, fieldAccessor.getIdent());
+                FieldAccessor outerFieldAccessExpr = new FieldAccessor(innerFieldAccessExpr, fieldAccessIdent);
+                outerFieldAccessExpr.setSourceLocation(fieldAccessor.getSourceLocation());
+                return outerFieldAccessExpr;
 
             } else if (fieldAccessorExpr.getKind() == Expression.Kind.VARIABLE_EXPRESSION) {
                 VariableExpr fieldAccessVarExpr = (VariableExpr) fieldAccessorExpr;
                 VarIdentifier fieldAccessVar = SqlppVariableUtil.toUserDefinedVariableName(fieldAccessVarExpr.getVar());
-                if (userLiveVariables.contains(fieldAccessVarExpr.getVar())) {
-                    VariableExpr qualifyingVarExpr = new VariableExpr(qualifyingVar);
-                    FieldAccessor innerFieldAccessExpr = new FieldAccessor(qualifyingVarExpr, fieldAccessVar);
-                    return new FieldAccessor(innerFieldAccessExpr, fieldAccessor.getIdent());
+                if (sourceSelectLiveVariables.contains(fieldAccessVarExpr)) {
+                    VariableExpr qualifyingVariableCopy = deepCopyVisitor.visit(qualifyingVar, null);
+                    FieldAccessor innerFieldAccessExpr = new FieldAccessor(qualifyingVariableCopy, fieldAccessVar);
+                    FieldAccessor outerFieldAccessExpr = new FieldAccessor(innerFieldAccessExpr, fieldAccessIdent);
+                    outerFieldAccessExpr.setSourceLocation(fieldAccessor.getSourceLocation());
+                    return outerFieldAccessExpr;
                 }
             }
             return super.visit(fieldAccessor, arg);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostRewriteCheckVisitor.java
similarity index 89%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostRewriteCheckVisitor.java
index a1ae83d..bc102aa 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PostRewriteCheckVisitor.java
@@ -16,12 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
@@ -31,9 +30,11 @@
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.FromTerm;
 import org.apache.asterix.lang.sqlpp.clause.JoinClause;
 import org.apache.asterix.lang.sqlpp.clause.NestClause;
@@ -42,7 +43,7 @@
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
 
 /**
- * Throw an error if a graph AST node (that isn't a special instance of {@link FromGraphClause}) is ever encountered.
+ * Throw an error if we encounter a Graphix AST node that isn't a {@link FromGraphClause} that has been lowered.
  */
 public class PostRewriteCheckVisitor extends AbstractSqlppSimpleExpressionVisitor
         implements IGraphixLangVisitor<Expression, ILangExpression> {
@@ -82,20 +83,23 @@
     }
 
     @Override
-    public Expression visit(GraphSelectBlock gsb, ILangExpression arg) throws CompilationException {
-        // The only Graph AST node that should survive is the GRAPH-SELECT-BLOCK, which should functionally act the same
-        // as its parent class SELECT-BLOCK.
-        if (!gsb.hasFromClause() || gsb.hasFromGraphClause()) {
-            return throwException(gsb);
+    public Expression visit(FromClause fc, ILangExpression arg) throws CompilationException {
+        if (fc instanceof FromGraphClause) {
+            return visit((FromGraphClause) fc, arg);
 
         } else {
-            return null;
+            return super.visit(fc, arg);
         }
     }
 
     @Override
     public Expression visit(FromGraphClause fgc, ILangExpression arg) throws CompilationException {
-        return throwException(fgc);
+        if (fgc.getLowerClause() == null) {
+            return throwException(fgc);
+
+        } else {
+            return null;
+        }
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PreRewriteCheckVisitor.java
similarity index 68%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PreRewriteCheckVisitor.java
index be82df7..b58463b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PreRewriteCheckVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
 
@@ -34,29 +34,42 @@
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
 import org.apache.asterix.graphix.metadata.entity.schema.Graph;
 import org.apache.asterix.graphix.metadata.entity.schema.Schema;
 import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.api.exceptions.SourceLocation;
 
 /**
- * A pre-rewrite pass to validate our user query. We validate the following:
- * 1. An edge variable is not defined more than once. (e.g. (u)-[e]-(v), (w)-[e]-(y))
- * 2. A vertex variable is not defined more than once with labels. (e.g. (u:User)-[e]-(v), (u:User)-[]-(:Review))
- * 3. An edge label exists in the context of the edge's {@link FromGraphClause}.
- * 4. A vertex label exists in the context of the vertex's {@link FromGraphClause}.
- * 5. The minimum hops and maximum hops of a sub-path is not equal to zero.
- * 6. The maximum hops of a sub-path is greater than or equal to the minimum hops of the same sub-path.
- * 7. An anonymous / declared graph passes the same validation that a named graph does.
+ * A pre-rewrite pass to validate / raise warnings about our user query.
+ * <p>
+ * We validate the following:
+ * <ul>
+ *  <li>An edge variable is not defined more than once. (e.g. (u)-[e]-(v), (w)-[e]-(y))</li>
+ *  <li>A vertex variable is not defined more than once with labels. (e.g. (u:User)-[e]-(v), (u:User)-[]-(:Review))</li>
+ *  <li>An edge label exists in the context of the edge's {@link FromGraphClause}.</li>
+ *  <li>A vertex label exists in the context of the vertex's {@link FromGraphClause}.</li>
+ *  <li>The minimum hops and maximum hops of a sub-path is not equal to zero.</li>
+ *  <li>The maximum hops of a sub-path is greater than or equal to the minimum hops of the same sub-path.</li>
+ *  <li>An anonymous / declared graph passes the same validation that a named graph does.</li>
+ *  <li>That variables in an element filter expression do not reference other previously defined graph-elements.</li>
+ * </ul>
+ * <p>
+ * We raise warnings about the following:
+ * <ul>
+ *  <li>We encounter a disconnected pattern. TODO (GLENN): Implement this.</li>
+ * </ul>
  */
 public class PreRewriteCheckVisitor extends AbstractGraphixQueryVisitor {
     private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
@@ -64,10 +77,11 @@
 
     // Build new environments on each FROM-GRAPH-CLAUSE visit.
     private static class PreRewriteCheckEnvironment {
-        private final Set<ElementLabel> vertexLabels = new HashSet<>();
+        private final Set<ElementLabel> elementLabels = new HashSet<>();
         private final Set<ElementLabel> edgeLabels = new HashSet<>();
         private final Set<Identifier> vertexVariablesWithLabels = new HashSet<>();
         private final Set<Identifier> edgeVariables = new HashSet<>();
+        private final Set<Identifier> allElementVariables = new HashSet<>();
     }
 
     private final Map<ILangExpression, PreRewriteCheckEnvironment> environmentMap = new HashMap<>();
@@ -79,8 +93,7 @@
 
     @Override
     public Expression visit(GraphConstructor graphConstructor, ILangExpression arg) throws CompilationException {
-        DataverseName dataverseName = metadataProvider.getDefaultDataverseName();
-        GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphConstructor.getInstanceID());
+        GraphIdentifier graphIdentifier = ((FromGraphClause) arg).getGraphIdentifier(metadataProvider);
         Schema.Builder schemaBuilder = new Schema.Builder(graphIdentifier);
 
         // Perform the same validation we do for named graphs-- but don't build the schema object.
@@ -136,7 +149,7 @@
             Identifier graphName = fromGraphClause.getGraphName();
 
             // First, see if we can fetch the graph constructor from our declared graphs.
-            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
+            GraphIdentifier graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
             DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
             if (declaredGraph != null) {
                 graphConstructor = declaredGraph.getGraphConstructor();
@@ -152,10 +165,10 @@
 
                     } else {
                         graphFromMetadata.getGraphSchema().getVertices().stream().map(Vertex::getLabel)
-                                .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
+                                .forEach(environmentMap.get(fromGraphClause).elementLabels::add);
                         graphFromMetadata.getGraphSchema().getEdges().forEach(e -> {
-                            environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
-                            environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
+                            environmentMap.get(fromGraphClause).elementLabels.add(e.getSourceLabel());
+                            environmentMap.get(fromGraphClause).elementLabels.add(e.getDestinationLabel());
                             environmentMap.get(fromGraphClause).edgeLabels.add(e.getLabel());
                         });
                     }
@@ -168,13 +181,13 @@
         }
         if (graphConstructor != null) {
             graphConstructor.getVertexElements().stream().map(GraphConstructor.VertexConstructor::getLabel)
-                    .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
+                    .forEach(environmentMap.get(fromGraphClause).elementLabels::add);
             graphConstructor.getEdgeElements().forEach(e -> {
-                environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
-                environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
+                environmentMap.get(fromGraphClause).elementLabels.add(e.getSourceLabel());
+                environmentMap.get(fromGraphClause).elementLabels.add(e.getDestinationLabel());
                 environmentMap.get(fromGraphClause).edgeLabels.add(e.getEdgeLabel());
             });
-            graphConstructor.accept(this, arg);
+            graphConstructor.accept(this, fromGraphClause);
         }
 
         // We need to pass our FROM-GRAPH-CLAUSE to our MATCH-CLAUSE.
@@ -189,54 +202,88 @@
 
     @Override
     public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
-        for (ElementLabel vertexLabel : vertexExpression.getLabels()) {
-            if (!environmentMap.get(arg).vertexLabels.contains(vertexLabel)) {
+        PreRewriteCheckEnvironment workingEnvironment = environmentMap.get(arg);
+        Set<ElementLabel> environmentLabels = workingEnvironment.elementLabels;
+        for (ElementLabel elementLabel : vertexExpression.getLabels()) {
+            if (environmentLabels.stream().noneMatch(e -> e.getLabelName().equals(elementLabel.getLabelName()))) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertexExpression.getSourceLocation(),
-                        "Vertex label " + vertexLabel + " does not exist in the given graph schema.");
+                        "Vertex label " + elementLabel + " does not exist in the given graph schema.");
             }
         }
+        if (vertexExpression.getFilterExpr() != null) {
+            vertexExpression.getFilterExpr().accept(new AbstractGraphixQueryVisitor() {
+                @Override
+                public Expression visit(VariableExpr varExpr, ILangExpression arg) throws CompilationException {
+                    if (workingEnvironment.allElementVariables.contains(varExpr.getVar())) {
+                        SourceLocation sourceLocation = vertexExpression.getFilterExpr().getSourceLocation();
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLocation,
+                                "Cannot reference other graph elements in a filter expression! Consider putting your "
+                                        + "condition in a WHERE clause.");
+                    }
+                    return super.visit(varExpr, arg);
+                }
+            }, null);
+        }
         if (vertexExpression.getVariableExpr() != null && !vertexExpression.getLabels().isEmpty()) {
             Identifier vertexIdentifier = vertexExpression.getVariableExpr().getVar();
-            if (environmentMap.get(arg).vertexVariablesWithLabels.contains(vertexIdentifier)) {
+            if (workingEnvironment.vertexVariablesWithLabels.contains(vertexIdentifier)) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertexExpression.getSourceLocation(),
                         "Vertex " + vertexIdentifier + " defined with a label more than once. Labels can only be "
                                 + "bound to vertices once.");
             }
-            environmentMap.get(arg).vertexVariablesWithLabels.add(vertexIdentifier);
+            workingEnvironment.vertexVariablesWithLabels.add(vertexIdentifier);
+            workingEnvironment.allElementVariables.add(vertexIdentifier);
         }
         return vertexExpression;
     }
 
     @Override
     public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        PreRewriteCheckEnvironment workingEnvironment = environmentMap.get(arg);
         EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
-        if (environmentMap.get(arg).edgeLabels.isEmpty()) {
+        Set<ElementLabel> environmentLabels = workingEnvironment.edgeLabels;
+        if (environmentLabels.isEmpty()) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
                     "Query edge given, but no edge is defined in the schema.");
         }
 
         for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
-            if (!environmentMap.get(arg).edgeLabels.contains(edgeLabel)) {
+            if (environmentLabels.stream().noneMatch(e -> e.getLabelName().equals(edgeLabel.getLabelName()))) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
                         "Edge label " + edgeLabel + " does not exist in the given graph schema.");
             }
         }
+        if (edgeDescriptor.getFilterExpr() != null) {
+            edgeDescriptor.getFilterExpr().accept(new AbstractGraphixQueryVisitor() {
+                @Override
+                public Expression visit(VariableExpr varExpr, ILangExpression arg) throws CompilationException {
+                    if (workingEnvironment.allElementVariables.contains(varExpr.getVar())) {
+                        SourceLocation sourceLocation = edgeDescriptor.getFilterExpr().getSourceLocation();
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLocation,
+                                "Cannot reference other graph elements in a filter expression! Consider putting your "
+                                        + "condition in a WHERE clause.");
+                    }
+                    return super.visit(varExpr, arg);
+                }
+            }, null);
+        }
         if (edgeDescriptor.getVariableExpr() != null) {
             Identifier edgeIdentifier = edgeDescriptor.getVariableExpr().getVar();
-            if (environmentMap.get(arg).edgeVariables.contains(edgeIdentifier)) {
+            if (workingEnvironment.edgeVariables.contains(edgeIdentifier)) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
                         "Edge " + edgeIdentifier + " defined more than once. Edges can only connect two vertices.");
             }
-            environmentMap.get(arg).edgeVariables.add(edgeIdentifier);
+            workingEnvironment.edgeVariables.add(edgeIdentifier);
+            workingEnvironment.allElementVariables.add(edgeIdentifier);
         }
         if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH) {
             Integer minimumHops = edgeDescriptor.getMinimumHops();
             Integer maximumHops = edgeDescriptor.getMaximumHops();
-            if (maximumHops == 0 || (minimumHops != null && minimumHops == 0)) {
+            if ((maximumHops != null && maximumHops == 0) || (minimumHops != null && minimumHops == 0)) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
                         "Sub-path edges cannot have a hop length less than 1.");
 
-            } else if (minimumHops != null && (maximumHops < minimumHops)) {
+            } else if (minimumHops != null && maximumHops != null && maximumHops < minimumHops) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
                         "Sub-path edges cannot have a maximum hop length (" + maximumHops
                                 + ") less than the minimum hop length (" + minimumHops + ").");
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/QueryCanonicalizationVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/QueryCanonicalizationVisitor.java
new file mode 100644
index 0000000..caad9ef
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/QueryCanonicalizationVisitor.java
@@ -0,0 +1,209 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.algebra.compiler.option.ElementEvaluationOption;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.canonical.CanonicalElementBranchConsumer;
+import org.apache.asterix.graphix.lang.rewrite.canonical.CanonicalElementExpansionConsumer;
+import org.apache.asterix.graphix.lang.rewrite.canonical.CanonicalElementGeneratorFactory;
+import org.apache.asterix.graphix.lang.rewrite.common.BranchLookupTable;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.visitor.CheckSql92AggregateVisitor;
+
+/**
+ * Perform a canonicalization pass, which aims to list out unambiguous <i>navigational complex graph patterns</i>.
+ * <p>
+ * For each SELECT-EXPR, we perform the following:
+ * <ol>
+ *  <li>Collect all ambiguous graph elements in each SELECT-SET-OP.</li>
+ *  <li>Enumerate each canonical form of each ambiguous graph element.</li>
+ *  <li>Invoke one of two canonical element consumers for all ambiguous elements:
+ *   <ol>
+ *    <li>An expansion consumer, which will generate N UNION-ALL branches for N canonical forms of some element.</li>
+ *    <li>A branch consumer, which will populate a separate {@link BranchLookupTable} that holds all canonical forms of
+ *    some element.</li>
+ *   </ol></li>
+ *  <li>If our expansion consumer has fired, then we must ensure that the generated UNION-ALL does not affect any
+ *  grouping or sorts. Call {@link PostCanonicalExpansionVisitor}.</li>
+ * </ol>
+ */
+public class QueryCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
+    private final CheckSql92AggregateVisitor checkSql92AggregateVisitor;
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final BranchLookupTable branchLookupTable;
+
+    public QueryCanonicalizationVisitor(BranchLookupTable branchLookupTable,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.checkSql92AggregateVisitor = new CheckSql92AggregateVisitor();
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.branchLookupTable = branchLookupTable;
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        // Visit our SELECT-EXPR-level LET-CLAUSEs.
+        for (LetClause letClause : selectExpression.getLetList()) {
+            letClause.accept(this, selectExpression);
+        }
+
+        // First pass: collect all ambiguous graph elements.
+        AmbiguousElementVisitor ambiguousElementVisitor = new AmbiguousElementVisitor(graphixRewritingContext);
+        SelectSetOperation selectSetOperation = selectExpression.getSelectSetOperation();
+        selectSetOperation.accept(ambiguousElementVisitor, selectExpression);
+        if (ambiguousElementVisitor.getAmbiguousElements().isEmpty()) {
+            // We have no ambiguous elements. Our [sub]query is in canonical form, and we can exit here.
+            return selectExpression;
+        }
+
+        // Second pass: enumerate all canonical forms of each ambiguous element.
+        Map<AbstractExpression, List<? extends AbstractExpression>> canonicalElementMap = new HashMap<>();
+        for (AbstractExpression element : ambiguousElementVisitor.getAmbiguousElements()) {
+            CanonicalElementGeneratorFactory factory = ambiguousElementVisitor.getGeneratorFactory(element);
+            if (element instanceof VertexPatternExpr) {
+                VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) element;
+                List<VertexPatternExpr> canonicalVertices = factory.generateCanonicalVertices(vertexPatternExpr);
+                canonicalElementMap.put(element, canonicalVertices);
+
+            } else { // ambiguousElement instanceof EdgePatternExpr
+                EdgePatternExpr edgePatternExpr = (EdgePatternExpr) element;
+                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+                if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
+                    List<EdgePatternExpr> canonicalEdges = factory.generateCanonicalEdges(edgePatternExpr);
+                    canonicalElementMap.put(element, canonicalEdges);
+
+                } else { // edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
+                    ElementEvaluationOption option = ambiguousElementVisitor.getElementEvaluationOption(element);
+                    List<PathPatternExpr> canonicalPaths = factory.generateCanonicalPaths(edgePatternExpr,
+                            option == ElementEvaluationOption.SWITCH_AND_CYCLE);
+                    canonicalElementMap.put(element, canonicalPaths);
+                }
+            }
+        }
+
+        // Third pass: invoke the appropriate consumer for each canonical element.
+        SelectExpression selectExpressionCopy = graphixDeepCopyVisitor.visit(selectExpression, null);
+        CanonicalElementExpansionConsumer expansionConsumer =
+                new CanonicalElementExpansionConsumer(selectExpressionCopy, graphixRewritingContext);
+        CanonicalElementBranchConsumer branchConsumer = new CanonicalElementBranchConsumer(branchLookupTable);
+        Map<SelectBlock, List<AbstractExpression>> groupedElements = new HashMap<>();
+        ambiguousElementVisitor.getAmbiguousElements().forEach(e -> {
+            SelectBlock sourceSelectBlock = ambiguousElementVisitor.getSourceSelectBlock(e);
+            groupedElements.putIfAbsent(sourceSelectBlock, new ArrayList<>());
+            groupedElements.get(sourceSelectBlock).add(e);
+        });
+        for (Map.Entry<SelectBlock, List<AbstractExpression>> entry : groupedElements.entrySet()) {
+            expansionConsumer.resetSelectBlock(entry.getKey());
+            for (AbstractExpression ambiguousElement : entry.getValue()) {
+                ElementEvaluationOption option = ambiguousElementVisitor.getElementEvaluationOption(ambiguousElement);
+                if (option == ElementEvaluationOption.EXPAND_AND_UNION) {
+                    expansionConsumer.accept(ambiguousElement, canonicalElementMap.get(ambiguousElement));
+
+                } else { // option == ElementEvaluationOption.SWITCH_AND_CYCLE
+                    branchConsumer.accept(ambiguousElement, canonicalElementMap.get(ambiguousElement));
+                }
+            }
+        }
+
+        // Check if we have any output-modifiers / grouping, that we have no SET-OPs, and if expansion has occurred.
+        boolean hasRightInputs = selectSetOperation.hasRightInputs();
+        boolean hasOutputModifiers = selectExpression.hasLimit() || selectExpression.hasOrderby();
+        boolean hasGroupBy = false, hasAggregation = false;
+        SelectBlock originalLeftSelectBlock = selectSetOperation.getLeftInput().getSelectBlock();
+        if (selectSetOperation.getLeftInput().selectBlock()) {
+            hasGroupBy = originalLeftSelectBlock.hasGroupbyClause();
+            hasAggregation = checkSql92AggregateVisitor.visit(originalLeftSelectBlock, null);
+            hasAggregation |= originalLeftSelectBlock.getSelectClause().distinct();
+        }
+
+        // Finalize our expansion consumer.
+        Set<SelectBlock> generatedSelectBlocks = new HashSet<>();
+        expansionConsumer.finalize(selectExpression, generatedSelectBlocks::add);
+
+        // Perform a post-canonical expansion pass if necessary.
+        boolean hasExpansionOccurred = !generatedSelectBlocks.isEmpty();
+        if (hasExpansionOccurred && !hasRightInputs && (hasOutputModifiers | hasGroupBy | hasAggregation)) {
+            Set<VariableExpr> liveVariables = new HashSet<>();
+            FromGraphClause fromGraphClause = (FromGraphClause) originalLeftSelectBlock.getFromClause();
+            for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+                for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                    if (pathExpression.getVariableExpr() != null) {
+                        liveVariables.add(pathExpression.getVariableExpr());
+                    }
+                    for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                        VariableExpr vertexVariable = vertexExpression.getVariableExpr();
+                        liveVariables.add(vertexVariable);
+                    }
+                    for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                        VariableExpr edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr();
+                        liveVariables.add(edgeVariable);
+                    }
+                }
+            }
+            if (!fromGraphClause.getCorrelateClauses().isEmpty()) {
+                List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
+                for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
+                    VariableExpr bindingVariable = correlateClause.getRightVariable();
+                    liveVariables.add(bindingVariable);
+                }
+            }
+            if (originalLeftSelectBlock.hasLetWhereClauses()) {
+                for (AbstractClause abstractClause : originalLeftSelectBlock.getLetWhereList()) {
+                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) abstractClause;
+                        VariableExpr bindingVariable = letClause.getVarExpr();
+                        liveVariables.add(bindingVariable);
+                    }
+                }
+            }
+            return new PostCanonicalExpansionVisitor(graphixRewritingContext, originalLeftSelectBlock,
+                    generatedSelectBlocks, liveVariables).visit(selectExpression, arg);
+        }
+
+        // Return our current SELECT-EXPR.
+        return selectExpression;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SchemaEnrichmentVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SchemaEnrichmentVisitor.java
new file mode 100644
index 0000000..ca64259
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SchemaEnrichmentVisitor.java
@@ -0,0 +1,177 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isEdgeFunction;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isVertexFunction;
+import static org.apache.asterix.graphix.function.GraphixFunctionMap.getFunctionPrepare;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateEdgeOption;
+import org.apache.asterix.graphix.algebra.compiler.option.SchemaDecorateVertexOption;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.function.prepare.IFunctionPrepare;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.LowerListClause;
+import org.apache.asterix.graphix.lang.clause.LowerSwitchClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.common.BranchLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+/**
+ * Perform a pass to enrich any {@link LetClause} and {@link LowerSwitchClause} nodes with necessary schema
+ * information. Note that this process may overestimate the amount of enrichment actually required (to minimize this
+ * difference requires some form of equivalence classes @ the rewriter level).
+ */
+public class SchemaEnrichmentVisitor extends AbstractGraphixQueryVisitor {
+    private final ElementLookupTable elementLookupTable;
+    private final Set<FunctionIdentifier> functionIdentifiers;
+    private final Map<VariableExpr, Expression> expressionMap;
+    private final SelectBlock workingSelectBlock;
+    private final GraphIdentifier graphIdentifier;
+    private final BranchLookupTable branchLookupTable;
+
+    public SchemaEnrichmentVisitor(SchemaDecorateVertexOption vertexMode, SchemaDecorateEdgeOption edgeMode,
+            ElementLookupTable elementLookupTable, BranchLookupTable branchLookupTable, GraphIdentifier graphIdentifier,
+            SelectBlock workingSelectBlock, Set<FunctionIdentifier> functionIDs) {
+        this.graphIdentifier = graphIdentifier;
+        this.elementLookupTable = elementLookupTable;
+        this.workingSelectBlock = workingSelectBlock;
+        this.branchLookupTable = branchLookupTable;
+        this.expressionMap = new HashMap<>();
+
+        // If we always need to perform schema-enrichment, then we add all of our schema functions to this set.
+        this.functionIdentifiers = functionIDs;
+        if (vertexMode == SchemaDecorateVertexOption.ALWAYS) {
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.ELEMENT_LABEL);
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.VERTEX_DETAIL);
+        }
+        if (edgeMode == SchemaDecorateEdgeOption.ALWAYS) {
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.ELEMENT_LABEL);
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.EDGE_DETAIL);
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.EDGE_DIRECTION);
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX);
+            this.functionIdentifiers.add(GraphixFunctionIdentifiers.EDGE_DEST_VERTEX);
+        }
+    }
+
+    private void visitClauseCollection(ClauseCollection clauseCollection) throws CompilationException {
+        for (FunctionIdentifier functionIdentifier : functionIdentifiers) {
+            IFunctionPrepare functionPrepare = getFunctionPrepare(functionIdentifier);
+            for (LetClause representativeEdgeBinding : clauseCollection.getRepresentativeEdgeBindings()) {
+                VariableExpr rightVar = representativeEdgeBinding.getVarExpr();
+                Expression graphExpr = expressionMap.get(rightVar);
+                if (isEdgeFunction(functionIdentifier)) {
+                    Expression outputExpr = functionPrepare.prepare(representativeEdgeBinding.getBindingExpr(),
+                            graphExpr, graphIdentifier, elementLookupTable);
+                    representativeEdgeBinding.setBindingExpr(outputExpr);
+                }
+            }
+            for (LetClause representativeVertexBinding : clauseCollection.getRepresentativeVertexBindings()) {
+                VariableExpr rightVar = representativeVertexBinding.getVarExpr();
+                Expression graphExpr = expressionMap.get(rightVar);
+                if (isVertexFunction(functionIdentifier)) {
+                    Expression outputExpr = functionPrepare.prepare(representativeVertexBinding.getBindingExpr(),
+                            graphExpr, graphIdentifier, elementLookupTable);
+                    representativeVertexBinding.setBindingExpr(outputExpr);
+                }
+            }
+            for (Pair<VariableExpr, LetClause> eagerVertexBinding : clauseCollection.getEagerVertexBindings()) {
+                Expression graphExpr = expressionMap.get(eagerVertexBinding.first);
+                if (isVertexFunction(functionIdentifier)) {
+                    Expression outputExpr = functionPrepare.prepare(eagerVertexBinding.second.getBindingExpr(),
+                            graphExpr, graphIdentifier, elementLookupTable);
+                    eagerVertexBinding.second.setBindingExpr(outputExpr);
+                }
+            }
+            for (AbstractClause nonRepresentativeClause : clauseCollection.getNonRepresentativeClauses()) {
+                if (nonRepresentativeClause.getClauseType() == Clause.ClauseType.EXTENSION) {
+                    LowerSwitchClause lowerSwitchClause = (LowerSwitchClause) nonRepresentativeClause;
+                    for (ClauseCollection innerClauseCollection : lowerSwitchClause.getCollectionLookupTable()) {
+                        visitClauseCollection(innerClauseCollection);
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        // Visit our immediate MATCH AST nodes (do not visit lower levels).
+        FromGraphClause fromGraphClause = (FromGraphClause) selectBlock.getFromClause();
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            matchClause.accept(this, arg);
+        }
+
+        // We are going to enrich these LET-CLAUSEs & FIXED-POINT-CLAUSEs w/ the necessary schema.
+        if (workingSelectBlock.equals(selectBlock)) {
+            LowerListClause lowerClause = (LowerListClause) fromGraphClause.getLowerClause();
+            ClauseCollection clauseCollection = lowerClause.getClauseCollection();
+            visitClauseCollection(clauseCollection);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
+        VariableExpr variableExpr = vertexPatternExpr.getVariableExpr();
+        if (variableExpr != null) {
+            expressionMap.put(variableExpr, vertexPatternExpr);
+        }
+        return super.visit(vertexPatternExpr, arg);
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        VariableExpr variableExpr = edgeDescriptor.getVariableExpr();
+        if (variableExpr != null) {
+            expressionMap.put(variableExpr, edgePatternExpr);
+        }
+        if (branchLookupTable.getBranches(edgePatternExpr) != null) {
+            for (EdgePatternExpr branch : branchLookupTable.getBranches(edgePatternExpr)) {
+                branch.getLeftVertex().accept(this, arg);
+                branch.getRightVertex().accept(this, arg);
+                branch.accept(this, arg);
+            }
+        }
+        edgePatternExpr.getLeftVertex().accept(this, arg);
+        edgePatternExpr.getRightVertex().accept(this, arg);
+        return edgePatternExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SubqueryVertexJoinVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SubqueryVertexJoinVisitor.java
new file mode 100644
index 0000000..95993d5
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/SubqueryVertexJoinVisitor.java
@@ -0,0 +1,147 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.annotation.SubqueryVertexJoinAnnotation;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.context.Scope;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.parser.ScopeChecker;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+
+/**
+ * Search for Graphix sub-queries that have 'free' vertices, and see if we can explicitly JOIN them with a vertex in
+ * a parent query that refers to the same graph.
+ */
+public class SubqueryVertexJoinVisitor extends AbstractGraphixQueryVisitor {
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final Map<GraphIdentifier, ScopeChecker> vertexScopeMap;
+    private final Deque<GraphIdentifier> graphIdentifierStack;
+
+    public SubqueryVertexJoinVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.graphIdentifierStack = new ArrayDeque<>();
+        this.vertexScopeMap = new HashMap<>();
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        if (selectBlock.hasFromClause() && selectBlock.getFromClause() instanceof FromGraphClause) {
+            FromGraphClause fromGraphClause = (FromGraphClause) selectBlock.getFromClause();
+            MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+            GraphIdentifier graphIdentifier = fromGraphClause.getGraphIdentifier(metadataProvider);
+            vertexScopeMap.putIfAbsent(graphIdentifier, new ScopeChecker());
+            graphIdentifierStack.push(graphIdentifier);
+            vertexScopeMap.get(graphIdentifier).createNewScope();
+
+            // We want to provide our SELECT-BLOCK to our MATCH-CLAUSE.
+            super.visit(selectBlock, selectBlock);
+            graphIdentifierStack.pop();
+            vertexScopeMap.get(graphIdentifier).removeCurrentScope();
+
+        } else {
+            super.visit(selectBlock, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
+        GraphIdentifier workingGraphIdentifier = graphIdentifierStack.peek();
+        ScopeChecker workingScopeChecker = vertexScopeMap.get(workingGraphIdentifier);
+        Scope precedingScope = workingScopeChecker.getPrecedingScope();
+        Scope currentScope = workingScopeChecker.getCurrentScope();
+
+        Map<VariableExpr, VariableExpr> remappedVariables = new HashMap<>();
+        for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+            for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                VariableExpr vertexVariable = vertexExpression.getVariableExpr();
+                VarIdentifier vertexName = vertexVariable.getVar();
+                if (precedingScope == null || precedingScope.getParentScope() == null) {
+                    // We are in a top-level FROM-GRAPH-CLAUSE. Add this variable to our current scope.
+                    currentScope.addSymbolToScope(vertexName);
+
+                } else if (currentScope.findLocalSymbol(vertexName.getValue()) == null
+                        && currentScope.findSymbol(vertexName.getValue()) != null) {
+                    // We have a nested Graphix query, and we have found a vertex that references an outer vertex.
+                    SelectBlock parentSelectBlock = (SelectBlock) arg;
+
+                    // Replace the current vertex variable with a copy.
+                    VariableExpr newVariable = graphixRewritingContext.getGraphixVariableCopy(vertexVariable);
+                    vertexExpression.setVariableExpr(newVariable);
+                    vertexExpression.getVariableExpr().setSourceLocation(vertexVariable.getSourceLocation());
+                    vertexExpression.addHint(new SubqueryVertexJoinAnnotation(vertexVariable));
+                    remappedVariables.put(vertexVariable, newVariable);
+
+                    // JOIN our copy with the parent vertex variable.
+                    List<Expression> joinArgs = List.of(vertexVariable, newVariable);
+                    OperatorExpr joinExpr = new OperatorExpr(joinArgs, List.of(OperatorType.EQ), false);
+                    parentSelectBlock.getLetWhereList().add(new WhereClause(joinExpr));
+
+                } else if (currentScope.findLocalSymbol(vertexName.getValue()) == null) {
+                    // We have a nested Graphix query that does not correlate with an outer query.
+                    currentScope.addSymbolToScope(vertexName);
+
+                } else if (remappedVariables.containsKey(vertexVariable)) {
+                    // We have replaced this variable already. Update the reference.
+                    VariableExpr remappedVertexVariable = remappedVariables.get(vertexVariable);
+                    vertexExpression.setVariableExpr(graphixDeepCopyVisitor.visit(remappedVertexVariable, null));
+                    vertexExpression.getVariableExpr().setSourceLocation(vertexVariable.getSourceLocation());
+                    vertexExpression.addHint(new SubqueryVertexJoinAnnotation(vertexVariable));
+                }
+            }
+            for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                VertexPatternExpr leftVertex = edgeExpression.getLeftVertex();
+                VertexPatternExpr rightVertex = edgeExpression.getRightVertex();
+                if (remappedVariables.containsKey(leftVertex.getVariableExpr())) {
+                    VariableExpr leftVariableRemap = remappedVariables.get(leftVertex.getVariableExpr());
+                    leftVertex.setVariableExpr(graphixDeepCopyVisitor.visit(leftVariableRemap, null));
+                    leftVertex.addHint(new SubqueryVertexJoinAnnotation(leftVariableRemap));
+                }
+                if (remappedVariables.containsKey(rightVertex.getVariableExpr())) {
+                    VariableExpr rightVariableRemap = remappedVariables.get(rightVertex.getVariableExpr());
+                    rightVertex.setVariableExpr(graphixDeepCopyVisitor.visit(rightVariableRemap, null));
+                    rightVertex.addHint(new SubqueryVertexJoinAnnotation(rightVariableRemap));
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableRemapCloneVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableRemapCloneVisitor.java
new file mode 100644
index 0000000..4b9ba96
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableRemapCloneVisitor.java
@@ -0,0 +1,148 @@
+/*
+ * 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.asterix.graphix.lang.rewrite.visitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.VariableSubstitutionEnvironment;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.visitor.SqlppCloneAndSubstituteVariablesVisitor;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * An extension of {@link SqlppCloneAndSubstituteVariablesVisitor} that also remaps binding variables.
+ */
+public class VariableRemapCloneVisitor extends SqlppCloneAndSubstituteVariablesVisitor {
+    private final Map<VariableExpr, Expression> variableToExpressionMap = new HashMap<>();
+
+    public VariableRemapCloneVisitor(GraphixRewritingContext graphixRewritingContext) {
+        super(graphixRewritingContext);
+    }
+
+    public void addSubstitution(VariableExpr variable, Expression newExpr) {
+        variableToExpressionMap.put(variable, newExpr);
+    }
+
+    public void resetSubstitutions() {
+        variableToExpressionMap.clear();
+    }
+
+    public ILangExpression substitute(ILangExpression langExpression) throws CompilationException {
+        return langExpression.accept(this, createKeepSubstitutionEnvironment()).first;
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(FromTerm fromTerm,
+            VariableSubstitutionEnvironment env) throws CompilationException {
+        Pair<ILangExpression, VariableSubstitutionEnvironment> pair = super.visit(fromTerm, env);
+        if (env.constainsOldVar(fromTerm.getLeftVariable())
+                || (fromTerm.hasPositionalVariable() && env.constainsOldVar(fromTerm.getPositionalVariable()))) {
+            FromTerm fromTermAfterVisit = (FromTerm) pair.first;
+            VariableExpr remapLeftVariable = (VariableExpr) rewriteVariableExpr(fromTerm.getLeftVariable(), env);
+            VariableExpr remapPositionalVariable = fromTerm.hasPositionalVariable()
+                    ? (VariableExpr) rewriteVariableExpr(fromTerm.getPositionalVariable(), env) : null;
+            FromTerm fromTermWithRemapVariable = new FromTerm(fromTermAfterVisit.getLeftExpression(), remapLeftVariable,
+                    remapPositionalVariable, fromTermAfterVisit.getCorrelateClauses());
+            fromTermWithRemapVariable.setSourceLocation(fromTermAfterVisit.getSourceLocation());
+            return new Pair<>(fromTermWithRemapVariable, pair.second);
+
+        } else {
+            return pair;
+        }
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(JoinClause joinClause,
+            VariableSubstitutionEnvironment env) throws CompilationException {
+        Pair<ILangExpression, VariableSubstitutionEnvironment> pair = super.visit(joinClause, env);
+        if (env.constainsOldVar(joinClause.getRightVariable())
+                || (joinClause.hasPositionalVariable() && env.constainsOldVar(joinClause.getPositionalVariable()))) {
+            JoinClause joinClauseAfterVisit = (JoinClause) pair.first;
+            VariableExpr remapRightVariable = (VariableExpr) rewriteVariableExpr(joinClause.getRightVariable(), env);
+            VariableExpr remapPositionalVariable = joinClause.hasPositionalVariable()
+                    ? (VariableExpr) rewriteVariableExpr(joinClause.getPositionalVariable(), env) : null;
+
+            // A new environment gets created for our condition expression in our parent, so we need to revisit here.
+            ILangExpression remapConditionExpression = joinClause.getConditionExpression().accept(this, env).first;
+            JoinClause joinClauseWithRemapVariable = new JoinClause(joinClauseAfterVisit.getJoinType(),
+                    joinClauseAfterVisit.getRightExpression(), remapRightVariable, remapPositionalVariable,
+                    (Expression) remapConditionExpression, joinClauseAfterVisit.getOuterJoinMissingValueType());
+            joinClauseWithRemapVariable.setSourceLocation(joinClauseAfterVisit.getSourceLocation());
+            return new Pair<>(joinClauseWithRemapVariable, pair.second);
+
+        } else {
+            return pair;
+        }
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(UnnestClause unnestClause,
+            VariableSubstitutionEnvironment env) throws CompilationException {
+        Pair<ILangExpression, VariableSubstitutionEnvironment> pair = super.visit(unnestClause, env);
+        if (env.constainsOldVar(unnestClause.getRightVariable()) || (unnestClause.hasPositionalVariable()
+                && env.constainsOldVar(unnestClause.getPositionalVariable()))) {
+            UnnestClause unnestClauseAfterVisit = (UnnestClause) pair.first;
+            VariableExpr remapRightVariable = (VariableExpr) rewriteVariableExpr(unnestClause.getRightVariable(), env);
+            VariableExpr remapPositionalVariable = unnestClause.hasPositionalVariable()
+                    ? (VariableExpr) rewriteVariableExpr(unnestClause.getPositionalVariable(), env) : null;
+            UnnestClause unnestClauseWithRemapVariable = new UnnestClause(unnestClauseAfterVisit.getUnnestType(),
+                    unnestClauseAfterVisit.getRightExpression(), remapRightVariable, remapPositionalVariable,
+                    unnestClauseAfterVisit.getOuterUnnestMissingValueType());
+            unnestClauseWithRemapVariable.setSourceLocation(unnestClauseAfterVisit.getSourceLocation());
+            return new Pair<>(unnestClauseWithRemapVariable, pair.second);
+
+        } else {
+            return pair;
+        }
+    }
+
+    @Override
+    public Pair<ILangExpression, VariableSubstitutionEnvironment> visit(LetClause letClause,
+            VariableSubstitutionEnvironment env) throws CompilationException {
+        Pair<ILangExpression, VariableSubstitutionEnvironment> pair = super.visit(letClause, env);
+        if (env.constainsOldVar(letClause.getVarExpr())) {
+            LetClause letClauseAfterVisit = (LetClause) pair.first;
+            VariableExpr remapVariable = (VariableExpr) rewriteVariableExpr(letClause.getVarExpr(), env);
+            LetClause letClauseWithRemapVariable = new LetClause(remapVariable, letClauseAfterVisit.getBindingExpr());
+            letClauseWithRemapVariable.setSourceLocation(letClauseAfterVisit.getSourceLocation());
+            return new Pair<>(letClauseWithRemapVariable, pair.second);
+
+        } else {
+            return pair;
+        }
+    }
+
+    private VariableSubstitutionEnvironment createKeepSubstitutionEnvironment() {
+        return new VariableSubstitutionEnvironment(variableToExpressionMap) {
+            @Override
+            public void removeSubstitution(VariableExpr oldVar) {
+                // We don't want to remove our substitution here.
+            }
+        };
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableScopingCheckVisitor.java
similarity index 74%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableScopingCheckVisitor.java
index 2b01f9c..6d28200 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/VariableScopingCheckVisitor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.rewrite.visitor;
 
 import java.util.ArrayDeque;
 import java.util.Deque;
@@ -25,30 +25,31 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrite.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.clause.SelectClause;
 import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.CheckSql92AggregateVisitor;
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
 import org.apache.commons.lang3.mutable.Mutable;
 import org.apache.commons.lang3.mutable.MutableObject;
@@ -56,70 +57,52 @@
 /**
  * Ensure that any subtree whose immediate parent node includes a {@link FromGraphClause} follow Graphix-specific
  * variable resolution rules (i.e. do not rely on context variables).
- * - {@link PathPatternExpr} may introduce a variable. Uniqueness is enforced here.
- * - {@link EdgePatternExpr} may introduce a variable. Uniqueness is enforced w/ {@link PreRewriteCheckVisitor}.
- * - {@link VertexPatternExpr} may introduce a variable. Uniqueness is not required.
- * - {@link org.apache.asterix.lang.sqlpp.clause.JoinClause} may introduce a variable (handled in parent).
- * - {@link org.apache.asterix.lang.sqlpp.clause.NestClause} may introduce a variable (handled in parent).
- * - {@link org.apache.asterix.lang.sqlpp.clause.UnnestClause} may introduce a variable (handled in parent).
- * - {@link org.apache.asterix.lang.common.clause.GroupbyClause} may introduce a variable (handled in parent).
- * - {@link org.apache.asterix.lang.common.clause.LetClause} may introduce a variable (handled in parent).
+ * <ul>
+ *  <li>{@link PathPatternExpr} may introduce a variable. Uniqueness is enforced here.</li>
+ *  <li>{@link EdgePatternExpr} may introduce a variable. Uniqueness is enforced w/ {@link PreRewriteCheckVisitor}.</li>
+ *  <li>{@link VertexPatternExpr} may introduce a variable. Uniqueness is not required.</li>
+ *  <li>{@link org.apache.asterix.lang.sqlpp.clause.JoinClause} may introduce a variable (handled in parent).</li>
+ *  <li>{@link org.apache.asterix.lang.sqlpp.clause.NestClause} may introduce a variable (handled in parent).</li>
+ *  <li>{@link org.apache.asterix.lang.sqlpp.clause.UnnestClause} may introduce a variable (handled in parent).</li>
+ *  <li>{@link org.apache.asterix.lang.common.clause.GroupbyClause} may introduce a variable (handled in parent).</li>
+ *  <li>{@link org.apache.asterix.lang.common.clause.LetClause} may introduce a variable (handled in parent).</li>
+ * </ul>
  */
-public class ScopingCheckVisitor extends AbstractSqlppExpressionScopingVisitor
+public class VariableScopingCheckVisitor extends AbstractSqlppExpressionScopingVisitor
         implements IGraphixLangVisitor<Expression, ILangExpression> {
-    private final Deque<Mutable<Boolean>> graphixVisitStack = new ArrayDeque<>();
+    private final CheckSql92AggregateVisitor checkSql92AggregateVisitor = new CheckSql92AggregateVisitor();
+    private final Deque<Mutable<Boolean>> fromGraphClauseVisitStack = new ArrayDeque<>();
 
-    public ScopingCheckVisitor(GraphixRewritingContext graphixRewritingContext) {
+    public VariableScopingCheckVisitor(GraphixRewritingContext graphixRewritingContext) {
         super(graphixRewritingContext);
 
         // We start with an element of false in our stack.
-        graphixVisitStack.addLast(new MutableObject<>(false));
+        fromGraphClauseVisitStack.addLast(new MutableObject<>(false));
     }
 
     @Override
     public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        graphixVisitStack.addLast(new MutableObject<>(false));
+        fromGraphClauseVisitStack.addLast(new MutableObject<>(false));
         super.visit(selectExpression, arg);
-        graphixVisitStack.removeLast();
+        fromGraphClauseVisitStack.removeLast();
         return selectExpression;
     }
 
     @Override
-    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
-        return (selectBlock instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) selectBlock, arg)
-                : super.visit(selectBlock, arg);
-    }
+    public Expression visit(FromClause fromClause, ILangExpression arg) throws CompilationException {
+        if (fromClause instanceof FromGraphClause) {
+            return visit((FromGraphClause) fromClause, arg);
 
-    @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        graphixVisitStack.getLast().setValue(true);
-        if (graphSelectBlock.hasFromGraphClause()) {
-            graphSelectBlock.getFromGraphClause().accept(this, arg);
-
-        } else if (graphSelectBlock.hasFromClause()) {
-            graphSelectBlock.getFromClause().accept(this, arg);
+        } else {
+            return super.visit(fromClause, arg);
         }
-        if (graphSelectBlock.hasLetWhereClauses()) {
-            for (AbstractClause clause : graphSelectBlock.getLetWhereList()) {
-                clause.accept(this, arg);
-            }
-        }
-        if (graphSelectBlock.hasGroupbyClause()) {
-            graphSelectBlock.getGroupbyClause().accept(this, arg);
-        }
-        if (graphSelectBlock.hasLetHavingClausesAfterGroupby()) {
-            for (AbstractClause clause : graphSelectBlock.getLetHavingListAfterGroupby()) {
-                clause.accept(this, arg);
-            }
-        }
-        graphSelectBlock.getSelectClause().accept(this, arg);
-        return null;
     }
 
     @Override
     public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
         // We are now working with a new scope.
         scopeChecker.createNewScope();
+        fromGraphClauseVisitStack.getLast().setValue(true);
         for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
             matchClause.accept(this, arg);
         }
@@ -167,6 +150,9 @@
         if (edgeDescriptor.getVariableExpr() != null) {
             scopeChecker.getCurrentScope().addNewVarSymbolToScope(edgeDescriptor.getVariableExpr().getVar());
         }
+        if (edgeDescriptor.getFilterExpr() != null) {
+            edgeDescriptor.getFilterExpr().accept(this, arg);
+        }
         return edgePatternExpr;
     }
 
@@ -175,9 +161,18 @@
         if (vertexPatternExpr.getVariableExpr() != null) {
             scopeChecker.getCurrentScope().addNewVarSymbolToScope(vertexPatternExpr.getVariableExpr().getVar());
         }
+        if (vertexPatternExpr.getFilterExpr() != null) {
+            vertexPatternExpr.getFilterExpr().accept(this, arg);
+        }
         return vertexPatternExpr;
     }
 
+    // We will defer the scope of SQL-92 aggregates to our AggregationSugarVisitor.
+    @Override
+    public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+        return (checkSql92AggregateVisitor.visit(callExpr, arg)) ? callExpr : super.visit(callExpr, arg);
+    }
+
     // We aren't going to inline our column aliases (yet), so add our select variables to our scope.
     @Override
     public Expression visit(SelectClause selectClause, ILangExpression arg) throws CompilationException {
@@ -194,7 +189,8 @@
 
     @Override
     public Expression visit(VariableExpr varExpr, ILangExpression arg) throws CompilationException {
-        boolean hasVisitedGraphixNode = !graphixVisitStack.isEmpty() && graphixVisitStack.getLast().getValue();
+        boolean hasVisitedGraphixNode =
+                !fromGraphClauseVisitStack.isEmpty() && fromGraphClauseVisitStack.getLast().getValue();
         String varSymbol = varExpr.getVar().getValue();
 
         // We will only throw an unresolved error if we first encounter a Graphix AST node.
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java
deleted file mode 100644
index 86fc0ce..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.Collection;
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.print.SqlppASTPrintQueryVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.CanonicalExpansionVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementLookupTableVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.FunctionResolutionVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixFunctionCallVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GroupByAggSugarVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.PopulateUnknownsVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.PostRewriteCheckVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.PostRewriteVariableVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.PreRewriteCheckVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ScopingCheckVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.StructureAnalysisVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.StructureResolutionVisitor;
-import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.IParserFactory;
-import org.apache.asterix.lang.common.base.IReturningStatement;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.statement.Query;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.common.util.ExpressionUtils;
-import org.apache.asterix.lang.sqlpp.rewrites.SqlppFunctionBodyRewriter;
-import org.apache.asterix.lang.sqlpp.rewrites.SqlppQueryRewriter;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.Dataverse;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * Rewriter for Graphix queries, which will lower all graph AST nodes into SQL++ AST nodes. We perform the following:
- * 1. Perform an error-checking on the fresh AST (immediately after parsing).
- * 2. Populate the unknowns in our AST (e.g. vertex / edge variables, projections, GROUP-BY keys).
- * 3. Perform a variable-scoping pass to identify illegal variables (either duplicate or out-of-scope).
- * 4. Resolve all of our function calls (Graphix, SQL++, and user-defined).
- * 5. Perform resolution of unlabeled vertices / edges, as well as edge directions.
- * 6. Using the labels of the vertices / edges in our AST, fetch the relevant graph elements from our metadata.
- * 7. Perform a canonical Graphix lowering pass to remove ambiguities (e.g. undirected edges).
- * 8. Perform a lowering pass to transform Graphix AST nodes to SQL++ AST nodes.
- * 9. Perform another lowering pass to transform Graphix CALL-EXPR nodes to SQL++ AST nodes.
- * 10. Perform all SQL++ rewrites on our newly lowered AST.
- */
-public class GraphixQueryRewriter extends SqlppQueryRewriter {
-    private static final Logger LOGGER = LogManager.getLogger(GraphixQueryRewriter.class);
-
-    private final GraphixParserFactory parserFactory;
-    private final SqlppQueryRewriter bodyRewriter;
-
-    public GraphixQueryRewriter(IParserFactory parserFactory) {
-        super(parserFactory);
-        this.parserFactory = (GraphixParserFactory) parserFactory;
-        this.bodyRewriter = getFunctionAndViewBodyRewriter();
-    }
-
-    @Override
-    public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
-            boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
-            throws CompilationException {
-        LOGGER.debug("Starting Graphix AST rewrites.");
-
-        // Perform an initial error-checking pass to validate our user query.
-        LOGGER.trace("Performing pre-Graphix-rewrite check (user query validation).");
-        GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
-        PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(preRewriteCheckVisitor, null);
-
-        // Perform the Graphix rewrites.
-        rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
-
-        // Sanity check: ensure that no graph AST nodes exist after this point.
-        LOGGER.trace("Performing post-Graphix-rewrite check (making sure no graph AST nodes exist).");
-        PostRewriteCheckVisitor postRewriteCheckVisitor = new PostRewriteCheckVisitor();
-        topStatement.getBody().accept(postRewriteCheckVisitor, null);
-
-        // Perform the remainder of the SQL++ rewrites.
-        LOGGER.debug("Ending Graphix AST rewrites. Now starting SQL++ AST rewrites.");
-        rewriteSQLPPASTNodes(langRewritingContext, topStatement, allowNonStoredUDFCalls, inlineUdfsAndViews,
-                externalVars);
-
-        // If desired, log the SQL++ query equivalent (i.e. turn the AST back into a query and log this).
-        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
-        String printRewriteMetadataKeyName = GraphixCompilationProvider.PRINT_REWRITE_METADATA_CONFIG;
-        String printRewriteOption = (String) metadataProvider.getConfig().get(printRewriteMetadataKeyName);
-        if ((printRewriteOption != null && printRewriteOption.equalsIgnoreCase("true")) || LOGGER.isTraceEnabled()) {
-            StringWriter stringWriter = new StringWriter();
-            PrintWriter printWriter = new PrintWriter(stringWriter);
-            new SqlppASTPrintQueryVisitor(printWriter).visit((Query) topStatement, null);
-            LOGGER.log(LOGGER.getLevel(), "Rewritten Graphix query: " + stringWriter);
-        }
-
-        // Update the variable counter on our context.
-        topStatement.setVarCounter(graphixRewritingContext.getVarCounter().get());
-        LOGGER.debug("Ending SQL++ AST rewrites.");
-    }
-
-    public void loadNormalizedGraphElement(GraphixRewritingContext graphixRewritingContext,
-            GraphElementDeclaration graphElementDeclaration) throws CompilationException {
-        if (graphElementDeclaration.getNormalizedBody() == null) {
-            Dataverse defaultDataverse = graphixRewritingContext.getMetadataProvider().getDefaultDataverse();
-            Dataverse targetDataverse;
-
-            // We might need to change our dataverse, if the element definition requires a different one.
-            DataverseName elementName = graphElementDeclaration.getIdentifier().getGraphIdentifier().getDataverseName();
-            if (elementName.equals(defaultDataverse.getDataverseName())) {
-                targetDataverse = defaultDataverse;
-
-            } else {
-                try {
-                    targetDataverse = graphixRewritingContext.getMetadataProvider().findDataverse(elementName);
-
-                } catch (AlgebricksException e) {
-                    throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e,
-                            graphElementDeclaration.getSourceLocation(), elementName);
-                }
-            }
-            graphixRewritingContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
-
-            // Get the body of the rewritten query.
-            Expression rawBody = graphElementDeclaration.getRawBody();
-            try {
-                Query wrappedQuery =
-                        ExpressionUtils.createWrappedQuery(rawBody, graphElementDeclaration.getSourceLocation());
-                bodyRewriter.rewrite(graphixRewritingContext, wrappedQuery, false, false, List.of());
-                graphElementDeclaration.setNormalizedBody(wrappedQuery.getBody());
-
-            } catch (CompilationException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, rawBody.getSourceLocation(),
-                        "Bad definition for a graph element: " + e.getMessage());
-
-            } finally {
-                // Switch back to the working dataverse.
-                graphixRewritingContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
-            }
-        }
-    }
-
-    public void rewriteGraphixASTNodes(GraphixRewritingContext graphixRewritingContext,
-            IReturningStatement topStatement, boolean allowNonStoredUDFCalls) throws CompilationException {
-        // Generate names for unnamed graph elements, projections in our SELECT CLAUSE, and keys in our GROUP BY.
-        LOGGER.trace("Populating unknowns (both graph and non-graph) in our AST.");
-        PopulateUnknownsVisitor populateUnknownsVisitor = new PopulateUnknownsVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(populateUnknownsVisitor, null);
-
-        // Verify that variables are properly within scope.
-        LOGGER.trace("Verifying that variables are unique and are properly scoped.");
-        ScopingCheckVisitor scopingCheckVisitor = new ScopingCheckVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(scopingCheckVisitor, null);
-
-        // Resolve all of our (Graphix, SQL++, and user-defined) function calls.
-        LOGGER.trace("Resolving Graphix, SQL++, and user-defined function calls.");
-        FunctionResolutionVisitor functionResolutionVisitor =
-                new FunctionResolutionVisitor(graphixRewritingContext, allowNonStoredUDFCalls);
-        topStatement.getBody().accept(functionResolutionVisitor, null);
-
-        // Attempt to resolve our vertex labels, edge labels, and edge directions.
-        LOGGER.trace("Performing label and edge direction resolution.");
-        StructureResolutionVisitor structureResolutionVisitor = new StructureResolutionVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(structureResolutionVisitor, null);
-
-        // Fetch all relevant graph element declarations, using the element labels.
-        LOGGER.trace("Fetching relevant edge and vertex bodies from our graph schema.");
-        ElementLookupTable elementLookupTable = new ElementLookupTable();
-        ElementLookupTableVisitor elementLookupTableVisitor =
-                new ElementLookupTableVisitor(graphixRewritingContext, elementLookupTable, parserFactory);
-        topStatement.getBody().accept(elementLookupTableVisitor, null);
-        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
-            loadNormalizedGraphElement(graphixRewritingContext, graphElementDeclaration);
-        }
-
-        // Expand vertex and edge patterns to remove all ambiguities (into a canonical form).
-        LOGGER.trace("Expanding vertex and edge patterns to a canonical form.");
-        CanonicalExpansionVisitor canonicalExpansionVisitor = new CanonicalExpansionVisitor(graphixRewritingContext);
-        topStatement.setBody(topStatement.getBody().accept(canonicalExpansionVisitor, null));
-
-        // Perform an analysis pass to get our edge dependencies, dangling vertices, and FROM-GRAPH-CLAUSE variables.
-        LOGGER.trace("Collecting edge dependencies, dangling vertices, and FROM-GRAPH-CLAUSE specific variables.");
-        StructureAnalysisVisitor structureAnalysisVisitor = new StructureAnalysisVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(structureAnalysisVisitor, null);
-
-        // Transform all graph AST nodes (i.e. perform the representation lowering).
-        LOGGER.trace("Lowering the Graphix AST-specific nodes representation to a pure SQL++ representation.");
-        GraphixLoweringVisitor graphixLoweringVisitor = new GraphixLoweringVisitor(graphixRewritingContext,
-                elementLookupTable, structureAnalysisVisitor.getFromGraphClauseContextMap());
-        topStatement.setBody(topStatement.getBody().accept(graphixLoweringVisitor, null));
-
-        // Lower all of our Graphix function calls (and perform schema-enrichment).
-        LOGGER.trace("Lowering the Graphix CALL-EXPR nodes to a pure SQL++ representation.");
-        GraphixFunctionCallVisitor graphixFunctionCallVisitor = new GraphixFunctionCallVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(graphixFunctionCallVisitor, null);
-    }
-
-    public void rewriteSQLPPASTNodes(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
-            boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
-            throws CompilationException {
-        super.setup(langRewritingContext, topStatement, externalVars, allowNonStoredUDFCalls, inlineUdfsAndViews);
-        super.substituteGroupbyKeyExpression();
-        super.rewriteGroupBys();
-        super.inlineColumnAlias();
-        super.rewriteWindowExpressions();
-        super.rewriteGroupingSets();
-
-        // We need to override the default behavior of our variable check + rewrite visitor...
-        PostRewriteVariableVisitor postRewriteVariableVisitor = new PostRewriteVariableVisitor(langRewritingContext,
-                langRewritingContext.getMetadataProvider(), externalVars);
-        topStatement.accept(postRewriteVariableVisitor, null);
-        super.extractAggregatesFromCaseExpressions();
-
-        // ...and our GROUP-BY rewrite visitor.
-        GroupByAggSugarVisitor groupByAggSugarVisitor = new GroupByAggSugarVisitor(langRewritingContext, externalVars);
-        topStatement.accept(groupByAggSugarVisitor, null);
-
-        // The remainder of our rewrites are the same.
-        super.rewriteWindowAggregationSugar();
-        super.rewriteOperatorExpression();
-        super.rewriteCaseExpressions();
-        super.rewriteListInputFunctions();
-        super.rewriteRightJoins();
-        super.loadAndInlineUdfsAndViews();
-        super.rewriteSpecialFunctionNames();
-        super.inlineWithExpressions();
-    }
-
-    @Override
-    protected SqlppFunctionBodyRewriter getFunctionAndViewBodyRewriter() {
-        return new SqlppFunctionBodyRewriter(parserFactory) {
-            @Override
-            public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
-                    boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
-                    throws CompilationException {
-                // Perform an initial error-checking pass to validate our body.
-                LOGGER.trace("Performing pre-Graphix-rewrite check (user query validation).");
-                GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
-                PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(graphixRewritingContext);
-                topStatement.getBody().accept(preRewriteCheckVisitor, null);
-
-                // Perform the Graphix rewrites.
-                rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
-
-                // Sanity check: ensure that no graph AST nodes exist after this point.
-                LOGGER.trace("Performing post-Graphix-rewrite check (making sure no graph AST nodes exist).");
-                PostRewriteCheckVisitor postRewriteCheckVisitor = new PostRewriteCheckVisitor();
-                topStatement.getBody().accept(postRewriteCheckVisitor, null);
-
-                // Perform the remainder of the SQL++ rewrites.
-                LOGGER.debug("Ending Graphix AST rewrites. Now starting SQL++ AST rewrites.");
-                rewriteSQLPPASTNodes(langRewritingContext, topStatement, allowNonStoredUDFCalls, inlineUdfsAndViews,
-                        externalVars);
-                LOGGER.debug("Ending SQL++ AST rewrites.");
-
-                // Update the variable counter in our context.
-                topStatement.setVarCounter(langRewritingContext.getVarCounter().get());
-            }
-        };
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java
deleted file mode 100644
index 7e8bda5..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.statement.FunctionDecl;
-import org.apache.asterix.lang.common.statement.ViewDecl;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.hyracks.api.exceptions.IWarningCollector;
-
-/**
- * Wrapper class for {@link LangRewritingContext} and for Graphix specific rewriting.
- */
-public class GraphixRewritingContext extends LangRewritingContext {
-    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs = new HashMap<>();
-
-    public GraphixRewritingContext(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions,
-            List<ViewDecl> declaredViews, Set<DeclareGraphStatement> declareGraphStatements,
-            IWarningCollector warningCollector, int varCounter) {
-        super(metadataProvider, declaredFunctions, declaredViews, warningCollector, varCounter);
-        declareGraphStatements.forEach(d -> {
-            GraphIdentifier graphIdentifier = new GraphIdentifier(d.getDataverseName(), d.getGraphName());
-            this.declaredGraphs.put(graphIdentifier, d);
-        });
-    }
-
-    public Map<GraphIdentifier, DeclareGraphStatement> getDeclaredGraphs() {
-        return declaredGraphs;
-    }
-
-    public VarIdentifier getNewGraphixVariable() {
-        VarIdentifier langRewriteGeneratedVar = newVariable();
-        String graphixGeneratedName = "#GG_" + langRewriteGeneratedVar.getValue().substring(1);
-        return new VarIdentifier(graphixGeneratedName, langRewriteGeneratedVar.getId());
-    }
-
-    public static boolean isGraphixVariable(VarIdentifier varIdentifier) {
-        return varIdentifier.getValue().startsWith("#GG_");
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java
deleted file mode 100644
index 89a1d98..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.canonical;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Set;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-
-/**
- * Expand a disconnected vertex into a set of canonical disconnected vertices. For dangling vertices, this just
- * includes ensuring that each vertex has only one label.
- */
-public class DanglingVertexExpander implements ICanonicalExpander<VertexPatternExpr> {
-    private final GraphixDeepCopyVisitor deepCopyVisitor = new GraphixDeepCopyVisitor();
-
-    @Override
-    public void apply(VertexPatternExpr vertexPatternExpr, List<GraphSelectBlock> inputSelectBlocks)
-            throws CompilationException {
-        if (vertexPatternExpr.getLabels().size() == 1) {
-            // No expansion is necessary. Our vertex is in canonical form.
-            return;
-        }
-        VariableExpr originalVariableExpr = vertexPatternExpr.getVariableExpr();
-
-        // We want to end up with |GSBs| * |labels| number of generated GSBs.
-        List<GraphSelectBlock> generatedGraphSelectBlocks = new ArrayList<>();
-        for (GraphSelectBlock oldGraphSelectBlock : inputSelectBlocks) {
-            for (ElementLabel vertexLabel : vertexPatternExpr.getLabels()) {
-                GraphSelectBlock clonedSelectBlock = deepCopyVisitor.visit(oldGraphSelectBlock, null);
-                for (MatchClause matchClause : clonedSelectBlock.getFromGraphClause().getMatchClauses()) {
-                    for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
-                        VariableExpr newVertexVariableExpr = deepCopyVisitor.visit(originalVariableExpr, null);
-                        VertexPatternExpr newVertexPatternExpr =
-                                new VertexPatternExpr(newVertexVariableExpr, Set.of(vertexLabel));
-                        replaceVertexExpression(pathExpression.getVertexExpressions(),
-                                pathExpression.getEdgeExpressions(), vertexPatternExpr, newVertexPatternExpr);
-                    }
-                }
-                generatedGraphSelectBlocks.add(clonedSelectBlock);
-            }
-        }
-        inputSelectBlocks.clear();
-        inputSelectBlocks.addAll(generatedGraphSelectBlocks);
-    }
-
-    private static void replaceVertexExpression(List<VertexPatternExpr> vertexExpressions,
-            List<EdgePatternExpr> edgeExpressions, VertexPatternExpr oldVertexExpression,
-            VertexPatternExpr newVertexExpression) {
-        ListIterator<VertexPatternExpr> vertexExpressionIterator = vertexExpressions.listIterator();
-        while (vertexExpressionIterator.hasNext()) {
-            VertexPatternExpr workingVertexExpression = vertexExpressionIterator.next();
-            if (workingVertexExpression.equals(oldVertexExpression)) {
-                vertexExpressionIterator.set(newVertexExpression);
-                break;
-            }
-        }
-        for (EdgePatternExpr workingEdgeExpression : edgeExpressions) {
-            if (workingEdgeExpression.getLeftVertex().equals(oldVertexExpression)) {
-                workingEdgeExpression.setLeftVertex(newVertexExpression);
-
-            } else if (workingEdgeExpression.getRightVertex().equals(oldVertexExpression)) {
-                workingEdgeExpression.setRightVertex(newVertexExpression);
-            }
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java
deleted file mode 100644
index 3210919..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.canonical;
-
-import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.EdgeDirection;
-import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.PatternType;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Supplier;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
-import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.RecordConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.hyracks.algebricks.common.utils.Pair;
-
-/**
- * Expand an edge and its connected vertices into an edge in canonical form.
- * 1. An edge's left vertex must only have one label.
- * 2. An edge's right vertex must only have one label.
- * 3. An edge must only have one label.
- * 4. An edge must be directed.
- * 5. An edge must not be a sub-path.
- */
-public class EdgeSubPathExpander implements ICanonicalExpander<EdgePatternExpr> {
-    private final Supplier<VariableExpr> newVariableSupplier;
-    private final GraphixDeepCopyVisitor deepCopyVisitor;
-
-    // To avoid "over-expanding", we need to keep knowledge about our FROM-GRAPH-CLAUSE.
-    private SchemaKnowledgeTable schemaKnowledgeTable;
-
-    public EdgeSubPathExpander(Supplier<VarIdentifier> getNewVariable) {
-        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
-        this.newVariableSupplier = () -> new VariableExpr(getNewVariable.get());
-    }
-
-    public void resetSchema(SchemaKnowledgeTable schemaKnowledgeTable) {
-        this.schemaKnowledgeTable = schemaKnowledgeTable;
-    }
-
-    private static class CanonicalEdgeContext {
-        private final VariableExpr leftVertexVar;
-        private final VariableExpr rightVertexVar;
-        private final VariableExpr edgeVar;
-
-        private final ElementLabel leftVertexLabel;
-        private final ElementLabel rightVertexLabel;
-        private final ElementLabel edgeLabel;
-
-        private final EdgeDirection edgeDirection;
-
-        private CanonicalEdgeContext(VariableExpr leftVertexVar, VariableExpr rightVertexVar, VariableExpr edgeVar,
-                ElementLabel leftVertexLabel, ElementLabel rightVertexLabel, ElementLabel edgeLabel,
-                EdgeDirection edgeDirection) {
-            this.leftVertexVar = leftVertexVar;
-            this.rightVertexVar = rightVertexVar;
-            this.leftVertexLabel = leftVertexLabel;
-            this.rightVertexLabel = rightVertexLabel;
-            this.edgeDirection = edgeDirection;
-            this.edgeVar = edgeVar;
-            this.edgeLabel = edgeLabel;
-        }
-    }
-
-    @Override
-    public void apply(EdgePatternExpr edgePatternExpr, List<GraphSelectBlock> inputSelectBlocks)
-            throws CompilationException {
-        boolean doesLeftVertexRequireExpansion = edgePatternExpr.getLeftVertex().getLabels().size() > 1;
-        boolean doesRightVertexRequireExpansion = edgePatternExpr.getRightVertex().getLabels().size() > 1;
-        boolean doesEdgeRequireExpansion = edgePatternExpr.getEdgeDescriptor().getEdgeLabels().size() > 1
-                || edgePatternExpr.getEdgeDescriptor().getPatternType() != PatternType.EDGE
-                || edgePatternExpr.getEdgeDescriptor().getEdgeDirection() == EdgeDirection.UNDIRECTED;
-        if (!doesLeftVertexRequireExpansion && !doesRightVertexRequireExpansion && !doesEdgeRequireExpansion) {
-            // Our edge and both of our vertices are in canonical form.
-            return;
-        }
-
-        // Expand all possibilities that our edge pattern could represent.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
-        VertexPatternExpr workingRightVertex = edgePatternExpr.getRightVertex();
-        List<List<CanonicalEdgeContext>> pathCandidates = new ArrayList<>();
-        if (edgeDescriptor.getPatternType() == PatternType.EDGE) {
-            expandEdgePattern(workingLeftVertex, workingRightVertex, edgeDescriptor, edgeDescriptor::getVariableExpr)
-                    .forEach(e -> pathCandidates.add(List.of(e)));
-
-        } else { // edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
-            Deque<List<CanonicalEdgeContext>> pathCandidateStack = new ArrayDeque<>(List.of(List.of()));
-            for (int i = 0; i < edgeDescriptor.getMaximumHops(); i++) {
-                VertexPatternExpr nextInternalVertex = (i != edgeDescriptor.getMaximumHops() - 1)
-                        ? edgePatternExpr.getInternalVertices().get(i) : edgePatternExpr.getRightVertex();
-
-                // We will only yield a path if we have generated the minimum number of hops.
-                Deque<List<CanonicalEdgeContext>> generatedCandidateStack = new ArrayDeque<>();
-                boolean isYieldPath = i >= edgeDescriptor.getMinimumHops() - 1;
-                while (!pathCandidateStack.isEmpty()) {
-                    List<CanonicalEdgeContext> pathCandidate = pathCandidateStack.removeLast();
-                    if (isYieldPath) {
-                        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
-                        expandEdgePattern(workingLeftVertex, rightVertex, edgeDescriptor, newVariableSupplier)
-                                .forEach(e -> {
-                                    List<CanonicalEdgeContext> pathCopy = new ArrayList<>(pathCandidate);
-                                    pathCopy.add(e);
-                                    pathCandidates.add(pathCopy);
-                                });
-                    }
-                    expandEdgePattern(workingLeftVertex, nextInternalVertex, edgeDescriptor, newVariableSupplier)
-                            .forEach(e -> {
-                                List<CanonicalEdgeContext> pathCopy = new ArrayList<>(pathCandidate);
-                                pathCopy.add(e);
-                                generatedCandidateStack.addLast(pathCopy);
-                            });
-                }
-                pathCandidateStack.addAll(generatedCandidateStack);
-
-                // Move our vertex cursor.
-                workingLeftVertex = nextInternalVertex;
-            }
-        }
-
-        // We must now prune all paths that contain an edge that does not adhere to our schema.
-        pathCandidates.removeIf(this::isPathCandidateInvalid);
-
-        // Perform the expansion into GRAPH-SELECT-BLOCKs.
-        List<GraphSelectBlock> selectBlockList = expandSelectBlocks(edgePatternExpr, inputSelectBlocks, pathCandidates);
-        inputSelectBlocks.clear();
-        inputSelectBlocks.addAll(selectBlockList);
-    }
-
-    private List<GraphSelectBlock> expandSelectBlocks(EdgePatternExpr edgePatternExpr,
-            List<GraphSelectBlock> inputSelectBlocks, List<List<CanonicalEdgeContext>> pathCandidates)
-            throws CompilationException {
-        List<GraphSelectBlock> generatedGraphSelectBlocks = new ArrayList<>();
-        for (GraphSelectBlock oldGraphSelectBlock : inputSelectBlocks) {
-            for (List<CanonicalEdgeContext> pathCandidate : pathCandidates) {
-                GraphSelectBlock clonedSelectBlock = deepCopyVisitor.visit(oldGraphSelectBlock, null);
-                for (MatchClause matchClause : clonedSelectBlock.getFromGraphClause().getMatchClauses()) {
-                    Pair<PathPatternExpr, PathPatternExpr> pathPatternReplacePair = null;
-
-                    // We are only interested in the path that contains our already expanded edge.
-                    for (PathPatternExpr pathPatternExpr : matchClause.getPathExpressions()) {
-                        List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
-                        int edgeExprIndex = edgeExpressions.indexOf(edgePatternExpr);
-                        if (edgeExprIndex < 0) {
-                            continue;
-                        }
-
-                        // Note: in this case, we do not need to worry about dangling vertices.
-                        List<LetClause> reboundSubPathList = new ArrayList<>(pathPatternExpr.getReboundSubPathList());
-                        List<EdgePatternExpr> newEdgePatternExprList = new ArrayList<>();
-                        List<VertexPatternExpr> newVertexPatternExprList = new ArrayList<>();
-                        for (int i = 0; i < edgeExpressions.size(); i++) {
-                            EdgePatternExpr existingEdgeExpression = edgeExpressions.get(i);
-                            if (i != edgeExprIndex) {
-                                newEdgePatternExprList.add(existingEdgeExpression);
-                                newVertexPatternExprList.add(existingEdgeExpression.getLeftVertex());
-                                newVertexPatternExprList.add(existingEdgeExpression.getRightVertex());
-                                continue;
-                            }
-
-                            // Note: this will produce duplicate vertex expressions.
-                            List<VertexPatternExpr> subPathVertexList = new ArrayList<>();
-                            List<EdgePatternExpr> subPathEdgeList = new ArrayList<>();
-                            for (CanonicalEdgeContext edgeContext : pathCandidate) {
-                                VariableExpr newEdgeVar = deepCopyVisitor.visit(edgeContext.edgeVar, null);
-                                VariableExpr newLeftVar = deepCopyVisitor.visit(edgeContext.leftVertexVar, null);
-                                VariableExpr newRightVar = deepCopyVisitor.visit(edgeContext.rightVertexVar, null);
-                                EdgeDescriptor newDescriptor = new EdgeDescriptor(edgeContext.edgeDirection,
-                                        PatternType.EDGE, Set.of(edgeContext.edgeLabel), newEdgeVar, 1, 1);
-                                Set<ElementLabel> leftLabelSingleton = Set.of(edgeContext.leftVertexLabel);
-                                Set<ElementLabel> rightLabelSingleton = Set.of(edgeContext.rightVertexLabel);
-                                VertexPatternExpr leftVertex = new VertexPatternExpr(newLeftVar, leftLabelSingleton);
-                                VertexPatternExpr rightVertex = new VertexPatternExpr(newRightVar, rightLabelSingleton);
-                                EdgePatternExpr newEdge = new EdgePatternExpr(leftVertex, rightVertex, newDescriptor);
-
-                                // Update our path and our sub-path.
-                                newEdgePatternExprList.add(newEdge);
-                                newVertexPatternExprList.add(leftVertex);
-                                newVertexPatternExprList.add(rightVertex);
-                                subPathEdgeList.add(newEdge);
-                                subPathVertexList.add(leftVertex);
-                                subPathVertexList.add(rightVertex);
-                            }
-
-                            // Bind our sub-path to a path record.
-                            if (existingEdgeExpression.getEdgeDescriptor().getPatternType() == PatternType.PATH) {
-                                RecordConstructor pathRecord = new RecordConstructor();
-                                EdgeDescriptor edgeDescriptor = existingEdgeExpression.getEdgeDescriptor();
-                                PathPatternAction.buildPathRecord(subPathVertexList, subPathEdgeList, pathRecord);
-                                reboundSubPathList.add(new LetClause(edgeDescriptor.getVariableExpr(), pathRecord));
-                            }
-                        }
-
-                        // Build a new path pattern to replace our old path pattern.
-                        VariableExpr newPathVariable = null;
-                        if (pathPatternExpr.getVariableExpr() != null) {
-                            newPathVariable = deepCopyVisitor.visit(pathPatternExpr.getVariableExpr(), null);
-                        }
-                        PathPatternExpr newPathPatternExpr =
-                                new PathPatternExpr(newVertexPatternExprList, newEdgePatternExprList, newPathVariable);
-                        newPathPatternExpr.getReboundSubPathList().addAll(reboundSubPathList);
-                        pathPatternReplacePair = new Pair<>(pathPatternExpr, newPathPatternExpr);
-                    }
-
-                    // Finalize our expansion by replacing our path in our MATCH-CLAUSE.
-                    if (pathPatternReplacePair == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                                "Edge was expanded, but edge was not found in given SELECT-BLOCK!");
-                    }
-                    PathPatternExpr oldPathPattern = pathPatternReplacePair.first;
-                    PathPatternExpr newPathPattern = pathPatternReplacePair.second;
-                    matchClause.getPathExpressions().replaceAll(p -> (p == oldPathPattern) ? newPathPattern : p);
-                }
-                generatedGraphSelectBlocks.add(clonedSelectBlock);
-            }
-        }
-        return generatedGraphSelectBlocks;
-    }
-
-    private List<CanonicalEdgeContext> expandEdgePattern(VertexPatternExpr leftVertex, VertexPatternExpr rightVertex,
-            EdgeDescriptor edgeDescriptor, Supplier<VariableExpr> edgeVarSupplier) throws CompilationException {
-        List<CanonicalEdgeContext> edgeCandidates = new ArrayList<>();
-        for (ElementLabel leftVertexLabel : leftVertex.getLabels()) {
-            for (ElementLabel rightVertexLabel : rightVertex.getLabels()) {
-                for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
-                    if (edgeDescriptor.getEdgeDirection() != EdgeDirection.LEFT_TO_RIGHT) {
-                        VariableExpr edgeVar = deepCopyVisitor.visit(edgeVarSupplier.get(), null);
-                        VariableExpr leftVertexVar = deepCopyVisitor.visit(leftVertex.getVariableExpr(), null);
-                        VariableExpr rightVertexVar = deepCopyVisitor.visit(rightVertex.getVariableExpr(), null);
-                        CanonicalEdgeContext rightToLeftContext =
-                                new CanonicalEdgeContext(leftVertexVar, rightVertexVar, edgeVar, leftVertexLabel,
-                                        rightVertexLabel, edgeLabel, EdgeDirection.RIGHT_TO_LEFT);
-                        edgeCandidates.add(rightToLeftContext);
-                    }
-                    if (edgeDescriptor.getEdgeDirection() != EdgeDirection.RIGHT_TO_LEFT) {
-                        VariableExpr edgeVar = deepCopyVisitor.visit(edgeVarSupplier.get(), null);
-                        VariableExpr leftVertexVar = deepCopyVisitor.visit(leftVertex.getVariableExpr(), null);
-                        VariableExpr rightVertexVar = deepCopyVisitor.visit(rightVertex.getVariableExpr(), null);
-                        CanonicalEdgeContext leftToRightContext =
-                                new CanonicalEdgeContext(leftVertexVar, rightVertexVar, edgeVar, leftVertexLabel,
-                                        rightVertexLabel, edgeLabel, EdgeDirection.LEFT_TO_RIGHT);
-                        edgeCandidates.add(leftToRightContext);
-                    }
-                }
-            }
-        }
-        return edgeCandidates;
-    }
-
-    private boolean isPathCandidateInvalid(List<CanonicalEdgeContext> pathCandidate) {
-        for (CanonicalEdgeContext edgeCandidate : pathCandidate) {
-            for (SchemaKnowledgeTable.KnowledgeRecord knowledgeRecord : schemaKnowledgeTable) {
-                if (edgeCandidate.edgeDirection == EdgeDirection.LEFT_TO_RIGHT) {
-                    if (edgeCandidate.leftVertexLabel.equals(knowledgeRecord.getSourceVertexLabel())
-                            && edgeCandidate.edgeLabel.equals(knowledgeRecord.getEdgeLabel())
-                            && edgeCandidate.rightVertexLabel.equals(knowledgeRecord.getDestVertexLabel())) {
-                        return false;
-                    }
-
-                } else { // edgeCandidate.edgeDirection == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
-                    if (edgeCandidate.leftVertexLabel.equals(knowledgeRecord.getDestVertexLabel())
-                            && edgeCandidate.edgeLabel.equals(knowledgeRecord.getEdgeLabel())
-                            && edgeCandidate.rightVertexLabel.equals(knowledgeRecord.getSourceVertexLabel())) {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-        return true;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
deleted file mode 100644
index 25ec13b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.common;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
-
-/**
- * A two-level ordered list of {@link EdgePatternExpr} instances, whose order depends on the input edge-dependency
- * graphs (represented as an adjacency list). Ideally, we want to return the smallest set of Hamilton
- * {@link EdgePatternExpr} paths, but this is an NP-hard problem. Instead, we find the **first** set of paths that
- * visit each {@link EdgePatternExpr} once. This inner-order is dependent on the order of the adjacency map iterators.
- */
-public class EdgeDependencyGraph implements Iterable<Iterable<EdgePatternExpr>> {
-    private final List<Map<Identifier, List<Identifier>>> adjacencyMaps;
-    private final Map<Identifier, EdgePatternExpr> edgePatternMap;
-
-    public EdgeDependencyGraph(List<Map<Identifier, List<Identifier>>> adjacencyMaps,
-            Map<Identifier, EdgePatternExpr> edgePatternMap) {
-        this.adjacencyMaps = adjacencyMaps;
-        this.edgePatternMap = edgePatternMap;
-    }
-
-    @Override
-    public Iterator<Iterable<EdgePatternExpr>> iterator() {
-        List<Iterable<EdgePatternExpr>> edgePatternIterables = new ArrayList<>();
-        Set<Identifier> globalVisitedSet = new HashSet<>();
-
-        for (Map<Identifier, List<Identifier>> adjacencyMap : adjacencyMaps) {
-            Deque<Identifier> edgeStack = new ArrayDeque<>();
-            Set<Identifier> localVisitedSet = new HashSet<>();
-
-            if (!adjacencyMap.entrySet().isEmpty()) {
-                if (globalVisitedSet.isEmpty()) {
-                    // We start with the first inserted edge inserted into our graph, and continue from there.
-                    Iterator<Map.Entry<Identifier, List<Identifier>>> mapIterator = adjacencyMap.entrySet().iterator();
-                    Map.Entry<Identifier, List<Identifier>> seedEdge = mapIterator.next();
-                    edgeStack.addFirst(seedEdge.getKey());
-                    edgeStack.addAll(seedEdge.getValue());
-                    while (mapIterator.hasNext()) {
-                        Map.Entry<Identifier, List<Identifier>> followingEdge = mapIterator.next();
-                        for (Identifier followingEdgeDependent : followingEdge.getValue()) {
-                            if (!edgeStack.contains(followingEdgeDependent)) {
-                                edgeStack.addLast(followingEdgeDependent);
-                            }
-                        }
-                    }
-
-                } else {
-                    // This is not our first pass. Find a connecting edge to seed our stack.
-                    Set<Identifier> disconnectedExploredEdgeSet = new LinkedHashSet<>();
-                    for (Map.Entry<Identifier, List<Identifier>> workingEdge : adjacencyMap.entrySet()) {
-                        for (Identifier workingEdgeDependent : workingEdge.getValue()) {
-                            if (globalVisitedSet.contains(workingEdgeDependent) && edgeStack.isEmpty()) {
-                                edgeStack.addFirst(workingEdgeDependent);
-                                localVisitedSet.add(workingEdgeDependent);
-
-                            } else if (!globalVisitedSet.contains(workingEdgeDependent)) {
-                                disconnectedExploredEdgeSet.add(workingEdgeDependent);
-
-                            } else {
-                                localVisitedSet.add(workingEdgeDependent);
-                            }
-                        }
-                        if (workingEdge.getValue().isEmpty() && globalVisitedSet.contains(workingEdge.getKey())) {
-                            localVisitedSet.add(workingEdge.getKey());
-
-                        } else if (workingEdge.getValue().isEmpty()) {
-                            disconnectedExploredEdgeSet.add(workingEdge.getKey());
-                        }
-                    }
-                    disconnectedExploredEdgeSet.forEach(edgeStack::addLast);
-                }
-
-                // Build our iterable.
-                globalVisitedSet.addAll(edgeStack);
-                edgePatternIterables.add(() -> new Iterator<>() {
-                    @Override
-                    public boolean hasNext() {
-                        // We are done once we have visited every node.
-                        return localVisitedSet.size() != adjacencyMap.size();
-                    }
-
-                    @Override
-                    public EdgePatternExpr next() {
-                        Identifier edgeIdentifier = edgeStack.removeFirst();
-                        for (Identifier dependency : adjacencyMap.get(edgeIdentifier)) {
-                            if (!localVisitedSet.contains(dependency)) {
-                                edgeStack.addFirst(dependency);
-                            }
-                        }
-                        localVisitedSet.add(edgeIdentifier);
-                        return edgePatternMap.get(edgeIdentifier);
-                    }
-                });
-
-            } else {
-                edgePatternIterables.add(Collections.emptyList());
-            }
-        }
-
-        return edgePatternIterables.iterator();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java
deleted file mode 100644
index cfa4aae..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.common;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
-
-/**
- * Lookup table for {@link GraphElementDeclaration} instances, vertex keys, edge destination keys, and edge source
- * keys-- indexed by {@link GraphElementIdentifier} instances.
- */
-public class ElementLookupTable implements Iterable<GraphElementDeclaration> {
-    private final Map<GraphElementIdentifier, GraphElementDeclaration> graphElementDeclMap = new HashMap<>();
-    private final Map<GraphElementIdentifier, List<List<String>>> vertexKeyMap = new HashMap<>();
-    private final Map<GraphElementIdentifier, List<List<String>>> edgeDestKeysMap = new HashMap<>();
-    private final Map<GraphElementIdentifier, List<List<String>>> edgeSourceKeysMap = new HashMap<>();
-
-    public void put(GraphElementIdentifier identifier, GraphElementDeclaration graphElementDeclaration) {
-        graphElementDeclMap.put(identifier, graphElementDeclaration);
-    }
-
-    public void putVertexKey(GraphElementIdentifier identifier, List<List<String>> primaryKey) {
-        vertexKeyMap.put(identifier, primaryKey);
-    }
-
-    public void putEdgeKeys(GraphElementIdentifier identifier, List<List<String>> sourceKey,
-            List<List<String>> destinationKey) {
-        edgeSourceKeysMap.put(identifier, sourceKey);
-        edgeDestKeysMap.put(identifier, destinationKey);
-    }
-
-    public GraphElementDeclaration getElementDecl(GraphElementIdentifier identifier) {
-        return graphElementDeclMap.get(identifier);
-    }
-
-    public List<List<String>> getVertexKey(GraphElementIdentifier identifier) {
-        return vertexKeyMap.get(identifier);
-    }
-
-    public List<List<String>> getEdgeDestKey(GraphElementIdentifier identifier) {
-        return edgeDestKeysMap.get(identifier);
-    }
-
-    public List<List<String>> getEdgeSourceKey(GraphElementIdentifier identifier) {
-        return edgeSourceKeysMap.get(identifier);
-    }
-
-    @Override
-    public Iterator<GraphElementDeclaration> iterator() {
-        return graphElementDeclMap.values().iterator();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java
deleted file mode 100644
index ebb1406..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java
+++ /dev/null
@@ -1,537 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildVertexEdgeJoin;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.AbstractInlineAction;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.IEnvironmentAction;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
-import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.RecordConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.TrueLiteral;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-
-/**
- * Build {@link IEnvironmentAction} instances to manipulate a {@link LoweringEnvironment}.
- */
-public class EnvironmentActionFactory {
-    private final Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
-    private final ElementLookupTable elementLookupTable;
-    private final LoweringAliasLookupTable aliasLookupTable;
-    private final GraphixRewritingContext graphixRewritingContext;
-
-    // The following must be provided before any creation methods are used.
-    private GraphIdentifier graphIdentifier;
-
-    public EnvironmentActionFactory(Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap,
-            ElementLookupTable elementLookupTable, LoweringAliasLookupTable aliasLookupTable,
-            GraphixRewritingContext graphixRewritingContext) {
-        this.analysisContextMap = analysisContextMap;
-        this.elementLookupTable = elementLookupTable;
-        this.aliasLookupTable = aliasLookupTable;
-        this.graphixRewritingContext = graphixRewritingContext;
-    }
-
-    public void reset(GraphIdentifier graphIdentifier) {
-        this.graphIdentifier = graphIdentifier;
-        this.aliasLookupTable.reset();
-    }
-
-    /**
-     * @see PathPatternAction
-     */
-    public IEnvironmentAction buildPathPatternAction(PathPatternExpr pathPatternExpr) {
-        return new PathPatternAction(pathPatternExpr);
-    }
-
-    /**
-     * @see IsomorphismAction
-     */
-    public IEnvironmentAction buildIsomorphismAction(FromGraphClause fromGraphClause) throws CompilationException {
-        return new IsomorphismAction(graphixRewritingContext, fromGraphClause, aliasLookupTable);
-    }
-
-    /**
-     * Build an {@link IEnvironmentAction} to handle a dangling vertex / vertex that is (currently) disconnected.
-     * Even though we introduce CROSS-JOINs here, we will not actually perform this CROSS-JOIN if this is the first
-     * vertex we are lowering. There are three possible {@link IEnvironmentAction}s generated here:
-     * 1. An action for inlined vertices that have no projections.
-     * 2. An action for inlined vertices with projections.
-     * 3. An action for non-inlined vertices.
-     */
-    public IEnvironmentAction buildDanglingVertexAction(VertexPatternExpr vertexPatternExpr)
-            throws CompilationException {
-        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
-        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
-        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
-
-        // We should only be working with one identifier (given that we only have one label).
-        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
-        if (vertexElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
-        }
-        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
-        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
-        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Introduce our iteration expression.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our vertex body.
-                    super.apply(loweringEnvironment);
-
-                    // Bind our intermediate (join) variable and vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
-                    });
-                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
-                }
-            };
-
-        } else if (vertexAnalysisContext.isExpressionInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Introduce our iteration expression.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our vertex body.
-                    super.apply(loweringEnvironment);
-
-                    // Build a record constructor from our context to bind to our vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        RecordConstructor recordConstructor1 = buildRecordConstructor();
-                        RecordConstructor recordConstructor2 = buildRecordConstructor();
-                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeVertexBinding(vertexVar, recordConstructor2);
-                    });
-                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
-                }
-            };
-
-        } else {
-            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
-            return loweringEnvironment -> {
-                // Introduce our iteration expression.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
-                            new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
-                    clauseSequence.addMainClause(joinClause);
-                });
-
-                // Bind our intermediate (join) variable and vertex variable.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                    clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
-                });
-            };
-        }
-    }
-
-    /**
-     * Build an {@link IEnvironmentAction} to handle an edge that we can fold into (attach from) an already introduced
-     * vertex. A folded edge is implicitly inlined. There are two possible {@link IEnvironmentAction}s generated here:
-     * 1. An action for inlined, folded edges that have no projections.
-     * 2. An action for inlined, folded edges that have projections.
-     */
-    public IEnvironmentAction buildFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
-            EdgePatternExpr edgePatternExpr) throws CompilationException {
-        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
-        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
-        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
-
-        // We should only be working with one identifier (given that we only have one label).
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        if (edgeElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
-        }
-        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
-        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
-        if (edgeAnalysisContext.isSelectClauseInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
-                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
-
-                    // Inline our edge body.
-                    super.apply(loweringEnvironment);
-
-                    // Build a binding for our edge variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr elementVarExpr = new VariableExpr(elementVariable);
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        clauseSequence.addMainClause(new CorrLetClause(elementVarExpr, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(elementVariable));
-                    });
-                    aliasLookupTable.putIterationAlias(edgeVar, elementVariable);
-                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
-                }
-            };
-
-        } else {
-            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
-                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
-
-                    // Inline our edge body.
-                    super.apply(loweringEnvironment);
-
-                    // Build a record constructor from our context to bind to our edge and intermediate (join) var.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        RecordConstructor recordConstructor1 = buildRecordConstructor();
-                        RecordConstructor recordConstructor2 = buildRecordConstructor();
-                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, recordConstructor2);
-                    });
-                    aliasLookupTable.putIterationAlias(edgeVar, elementVariable);
-                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
-                }
-            };
-        }
-    }
-
-    /**
-     * Build an {@link IEnvironmentAction} to handle an edge that we cannot fold into an already introduced vertex.
-     * There are three possible {@link IEnvironmentAction}s generated here:
-     * 1. An action for inlined edges that have no projections.
-     * 2. An action for inlined edges that have projections.
-     * 3. An action for non-inlined edges.
-     */
-    public IEnvironmentAction buildNonFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
-            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
-            throws CompilationException {
-        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
-        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
-        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
-        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
-
-        // We should only be working with one edge identifier...
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        if (edgeElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
-        }
-        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
-        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
-        Expression datasetCallExpression = edgeAnalysisContext.getDatasetCallExpression();
-
-        // ...and only one vertex identifier (given that we only have one label).
-        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
-        if (vertexElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
-        }
-        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
-        if (edgeAnalysisContext.isExpressionInline() && edgeAnalysisContext.isSelectClauseInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Join our edge iteration variable to our vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
-                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                                buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our edge body.
-                    super.apply(loweringEnvironment);
-
-                    // Bind our intermediate (join) variable and edge variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(iterationVar));
-                    });
-                    aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
-                }
-            };
-
-        } else if (edgeAnalysisContext.isExpressionInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Join our edge iteration variable to our vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
-                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                                buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our edge body.
-                    super.apply(loweringEnvironment);
-
-                    // Build a record constructor from our context to bind to our edge variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        RecordConstructor recordConstructor1 = buildRecordConstructor();
-                        RecordConstructor recordConstructor2 = buildRecordConstructor();
-                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, recordConstructor2);
-                    });
-                    aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
-                }
-            };
-
-        } else {
-            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(edgeIdentifier);
-            return loweringEnvironment -> {
-                // Join our edge body to our vertex variable.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
-                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                            buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                            buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
-                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
-                            new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                    clauseSequence.addMainClause(joinClause);
-                });
-
-                // Bind our intermediate (join) variable and edge variable.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                    clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(iterationVar));
-                });
-                aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
-                aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
-            };
-        }
-    }
-
-    /**
-     * Build an {@link IEnvironmentAction} to introduce a WHERE-CLAUSE that will correlate a vertex and edge.
-     */
-    public IEnvironmentAction buildRawJoinVertexAction(VertexPatternExpr vertexPatternExpr,
-            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
-            throws CompilationException {
-        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
-        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
-
-        // We should only be working with one edge identifier...
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        if (edgeElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
-        }
-        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
-
-        // ...and only one vertex identifier (given that we only have one label).
-        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
-        if (vertexElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
-        }
-        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
-        return loweringEnvironment -> {
-            // No aliases need to be introduced, we just need to add a WHERE-CONJUNCT.
-            VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
-            VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
-            loweringEnvironment.acceptTransformer(clauseSequence -> {
-                Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                        buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                        buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
-                clauseSequence.addMainClause(new CorrWhereClause(vertexEdgeJoin));
-            });
-        };
-    }
-
-    /**
-     * Build an {@link IEnvironmentAction} to handle a vertex that is bound to an existing (already introduced) edge.
-     * There are three possible {@link IEnvironmentAction}s generated here:
-     * 1. An action for inlined vertices that have no projections.
-     * 2. An action for inlined vertices that have projections.
-     * 3. An action for non-inlined vertices.
-     */
-    public IEnvironmentAction buildBoundVertexAction(VertexPatternExpr vertexPatternExpr,
-            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
-            throws CompilationException {
-        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
-        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
-        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
-        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
-
-        // We should only be working with one edge identifier...
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        if (edgeElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
-        }
-        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
-
-        // ...and only one vertex identifier (given that we only have one label).
-        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
-        if (vertexElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
-        }
-        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
-        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
-        Expression datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
-        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Join our vertex iteration variable to our edge variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
-                        VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
-                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                                buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our vertex body.
-                    super.apply(loweringEnvironment);
-
-                    // Bind our intermediate (join) variable and vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
-                    });
-                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
-                }
-            };
-
-        } else if (vertexAnalysisContext.isExpressionInline()) {
-            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
-                @Override
-                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-                    // Join our vertex iteration variable to our edge variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
-                        VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
-                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                                buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
-                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
-                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                        clauseSequence.addMainClause(joinClause);
-                    });
-
-                    // Inline our vertex body.
-                    super.apply(loweringEnvironment);
-
-                    // Build a record constructor from our context to bind to our vertex variable.
-                    loweringEnvironment.acceptTransformer(clauseSequence -> {
-                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                        RecordConstructor recordConstructor1 = buildRecordConstructor();
-                        RecordConstructor recordConstructor2 = buildRecordConstructor();
-                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
-                        clauseSequence.addRepresentativeVertexBinding(vertexVar, recordConstructor2);
-                    });
-                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
-                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
-                }
-            };
-
-        } else {
-            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
-            return loweringEnvironment -> {
-                // Join our vertex body to our edge variable.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
-                    VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
-                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
-                            buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
-                            buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
-                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
-                            new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
-                    clauseSequence.addMainClause(joinClause);
-                });
-
-                // Bind our intermediate (join) variable and vertex variable.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
-                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
-                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
-                    clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
-                });
-                aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
-                aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
-            };
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java
deleted file mode 100644
index 0642c20..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-
-/**
- * Lookup table for JOIN and ITERATION aliases, indexed by their representative (i.e. element) variable identifiers.
- */
-public class LoweringAliasLookupTable {
-    private final Map<VarIdentifier, VarIdentifier> joinAliasMap = new HashMap<>();
-    private final Map<VarIdentifier, VarIdentifier> iterationAliasMap = new HashMap<>();
-
-    public void putJoinAlias(VarIdentifier elementVariable, VarIdentifier aliasVariable) {
-        joinAliasMap.put(elementVariable, aliasVariable);
-    }
-
-    public void putIterationAlias(VarIdentifier elementVariable, VarIdentifier aliasVariable) {
-        iterationAliasMap.put(elementVariable, aliasVariable);
-    }
-
-    public VarIdentifier getJoinAlias(VarIdentifier elementVariable) {
-        return joinAliasMap.get(elementVariable);
-    }
-
-    public VarIdentifier getIterationAlias(VarIdentifier elementVariable) {
-        return iterationAliasMap.get(elementVariable);
-    }
-
-    public void reset() {
-        joinAliasMap.clear();
-        iterationAliasMap.clear();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java
deleted file mode 100644
index dc9f594..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower;
-
-import static org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil.toUserDefinedVariableName;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.function.Consumer;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.action.IEnvironmentAction;
-import org.apache.asterix.graphix.lang.rewrites.lower.transform.CorrelatedClauseSequence;
-import org.apache.asterix.graphix.lang.rewrites.lower.transform.ISequenceTransformer;
-import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
-import org.apache.asterix.lang.common.base.Clause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.Literal;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.TrueLiteral;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.FromClause;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-import org.apache.asterix.lang.sqlpp.clause.SelectClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
-import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
-import org.apache.commons.collections4.IterableUtils;
-import org.apache.commons.collections4.IteratorUtils;
-import org.apache.hyracks.api.exceptions.IWarningCollector;
-import org.apache.hyracks.api.exceptions.SourceLocation;
-import org.apache.hyracks.api.exceptions.Warning;
-
-/**
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor
- */
-public class LoweringEnvironment {
-    private final GraphSelectBlock graphSelectBlock;
-    private final CorrelatedClauseSequence mainClauseSequence;
-    private final GraphixRewritingContext graphixRewritingContext;
-    private CorrelatedClauseSequence leftMatchClauseSequence;
-
-    public LoweringEnvironment(GraphSelectBlock graphSelectBlock, GraphixRewritingContext graphixRewritingContext) {
-        this.graphixRewritingContext = graphixRewritingContext;
-        this.mainClauseSequence = new CorrelatedClauseSequence();
-        this.graphSelectBlock = graphSelectBlock;
-        this.leftMatchClauseSequence = null;
-    }
-
-    public void acceptAction(IEnvironmentAction environmentAction) throws CompilationException {
-        environmentAction.apply(this);
-    }
-
-    public void acceptTransformer(ISequenceTransformer sequenceTransformer) throws CompilationException {
-        boolean isLeftMatch = leftMatchClauseSequence == null;
-        sequenceTransformer.accept(isLeftMatch ? mainClauseSequence : leftMatchClauseSequence);
-    }
-
-    public void beginLeftMatch() throws CompilationException {
-        if (leftMatchClauseSequence != null) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                    "LEFT-MATCH lowering is currently in progress!");
-        }
-        leftMatchClauseSequence = new CorrelatedClauseSequence();
-    }
-
-    public void endLeftMatch(IWarningCollector warningCollector) throws CompilationException {
-        VariableSubstitutionVisitor substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
-        VariableExpr nestingVariableExpr = new VariableExpr(graphixRewritingContext.getNewGraphixVariable());
-        final Consumer<VarIdentifier> substitutionAdder = v -> {
-            VariableExpr sourceNestingVariableExpr = new VariableExpr(nestingVariableExpr.getVar());
-            FieldAccessor fieldAccessor = new FieldAccessor(sourceNestingVariableExpr, v);
-            substitutionVisitor.addSubstitution(v, fieldAccessor);
-        };
-
-        // Build up our projection list.
-        List<Projection> projectionList = new ArrayList<>();
-        ListIterator<AbstractBinaryCorrelateClause> forProjectIterator = leftMatchClauseSequence.getMainIterator();
-        while (forProjectIterator.hasNext()) {
-            AbstractBinaryCorrelateClause workingClause = forProjectIterator.next();
-            if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
-                continue;
-            }
-            VarIdentifier rightVariable = workingClause.getRightVariable().getVar();
-            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, new VariableExpr(rightVariable),
-                    toUserDefinedVariableName(rightVariable).getValue()));
-            substitutionAdder.accept(rightVariable);
-        }
-
-        // Assemble a FROM-TERM from our LEFT-MATCH sequence (do not add our representatives).
-        JoinClause headCorrelateClause = (JoinClause) leftMatchClauseSequence.popMainClauseSequence();
-        ListIterator<AbstractBinaryCorrelateClause> mainIterator = leftMatchClauseSequence.getMainIterator();
-        List<AbstractBinaryCorrelateClause> correlateClauses = IteratorUtils.toList(mainIterator);
-        raiseCrossJoinWarning(correlateClauses, warningCollector, null);
-        FromTerm fromTerm = new FromTerm(headCorrelateClause.getRightExpression(),
-                headCorrelateClause.getRightVariable(), headCorrelateClause.getPositionalVariable(), correlateClauses);
-
-        // Nest our FROM-TERM.
-        SelectClause selectClause = new SelectClause(null, new SelectRegular(projectionList), false);
-        SelectBlock selectBlock = new SelectBlock(selectClause, new FromClause(List.of(fromTerm)), null, null, null);
-        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
-        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
-        SelectExpression selectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
-
-        // Attach our assembled sequence back to the main sequence.
-        Expression conditionExpression = headCorrelateClause.getConditionExpression();
-        Expression newConditionExpression = conditionExpression.accept(substitutionVisitor, null);
-        JoinClause leftJoinClause = new JoinClause(JoinType.LEFTOUTER, selectExpression, nestingVariableExpr, null,
-                newConditionExpression, Literal.Type.MISSING);
-        mainClauseSequence.addMainClause(leftJoinClause);
-
-        // Introduce our representative variables back into  our main sequence.
-        for (CorrLetClause representativeVertexBinding : leftMatchClauseSequence.getRepresentativeVertexBindings()) {
-            VarIdentifier representativeVariable = representativeVertexBinding.getRightVariable().getVar();
-            Expression rightExpression = representativeVertexBinding.getRightExpression();
-            Expression reboundExpression = rightExpression.accept(substitutionVisitor, null);
-            mainClauseSequence.addRepresentativeVertexBinding(representativeVariable, reboundExpression);
-        }
-        for (CorrLetClause representativeEdgeBinding : leftMatchClauseSequence.getRepresentativeEdgeBindings()) {
-            VarIdentifier representativeVariable = representativeEdgeBinding.getRightVariable().getVar();
-            Expression rightExpression = representativeEdgeBinding.getRightExpression();
-            Expression reboundExpression = rightExpression.accept(substitutionVisitor, null);
-            mainClauseSequence.addRepresentativeEdgeBinding(representativeVariable, reboundExpression);
-        }
-        leftMatchClauseSequence = null;
-    }
-
-    public void finalizeLowering(FromGraphClause fromGraphClause, IWarningCollector warningCollector) {
-        AbstractBinaryCorrelateClause headCorrelateClause = mainClauseSequence.popMainClauseSequence();
-        List<AbstractBinaryCorrelateClause> correlateClauses = IterableUtils.toList(mainClauseSequence);
-        raiseCrossJoinWarning(correlateClauses, warningCollector, fromGraphClause.getSourceLocation());
-        FromTerm outputFromTerm = new FromTerm(headCorrelateClause.getRightExpression(),
-                headCorrelateClause.getRightVariable(), headCorrelateClause.getPositionalVariable(), correlateClauses);
-        graphSelectBlock.setFromClause(new FromClause(List.of(outputFromTerm)));
-        graphSelectBlock.getFromClause().setSourceLocation(fromGraphClause.getSourceLocation());
-    }
-
-    private static void raiseCrossJoinWarning(List<AbstractBinaryCorrelateClause> correlateClauses,
-            IWarningCollector warningCollector, SourceLocation sourceLocation) {
-        for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
-            if (correlateClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
-                JoinClause joinClause = (JoinClause) correlateClause;
-                if (joinClause.getConditionExpression().getKind() == Expression.Kind.LITERAL_EXPRESSION) {
-                    LiteralExpr literalExpr = (LiteralExpr) joinClause.getConditionExpression();
-                    if (literalExpr.getValue().equals(TrueLiteral.INSTANCE) && warningCollector.shouldWarn()) {
-                        warningCollector.warn(Warning.of(sourceLocation, ErrorCode.COMPILATION_ERROR,
-                                "Potential disconnected pattern encountered! A CROSS-JOIN has been introduced."));
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java
deleted file mode 100644
index b82801b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower.action;
-
-import static org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
-import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.clause.WhereClause;
-import org.apache.asterix.lang.common.expression.FieldBinding;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.RecordConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.StringLiteral;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
-import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
-
-/**
- * Inline an element body into a {@link LoweringEnvironment}. This includes a) copying {@link UnnestClause},
- * {@link LetClause}, and {@link WhereClause} AST nodes from our body analysis, and b) creating
- * {@link RecordConstructor} AST nodes to inline {@link org.apache.asterix.lang.sqlpp.clause.SelectRegular} nodes.
- */
-public abstract class AbstractInlineAction implements IEnvironmentAction {
-    protected final GraphixRewritingContext graphixRewritingContext;
-    protected final ElementBodyAnalysisContext bodyAnalysisContext;
-
-    // This may be mutated by our child.
-    protected VarIdentifier elementVariable;
-
-    // The following is reset on each application.
-    private VariableSubstitutionVisitor substitutionVisitor;
-
-    protected AbstractInlineAction(GraphixRewritingContext graphixRewritingContext,
-            ElementBodyAnalysisContext bodyAnalysisContext, VarIdentifier elementVariable) {
-        this.graphixRewritingContext = graphixRewritingContext;
-        this.bodyAnalysisContext = bodyAnalysisContext;
-        this.elementVariable = elementVariable;
-    }
-
-    @Override
-    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-        // To inline, we need to ensure that we substitute variables accordingly.
-        substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
-        if (bodyAnalysisContext.getFromTermVariable() != null) {
-            VariableExpr fromTermVariableExpr = bodyAnalysisContext.getFromTermVariable();
-            VariableExpr elementVariableExpr = new VariableExpr(elementVariable);
-            substitutionVisitor.addSubstitution(fromTermVariableExpr.getVar(), elementVariableExpr);
-        }
-
-        // If we have any UNNEST clauses, we need to add these.
-        if (bodyAnalysisContext.getUnnestClauses() != null) {
-            for (AbstractBinaryCorrelateClause unnestClause : bodyAnalysisContext.getUnnestClauses()) {
-                VarIdentifier reboundUnnestVariableID = graphixRewritingContext.getNewGraphixVariable();
-                VariableExpr reboundVariableExpr = new VariableExpr(reboundUnnestVariableID);
-
-                // Remap this UNNEST-CLAUSE to include our new variables.
-                loweringEnvironment.acceptTransformer(clauseSequence -> {
-                    UnnestClause copiedClause = (UnnestClause) SqlppRewriteUtil.deepCopy(unnestClause);
-                    copiedClause.accept(substitutionVisitor, null);
-                    VariableExpr substitutionVariableExpr = (copiedClause.hasPositionalVariable())
-                            ? copiedClause.getPositionalVariable() : copiedClause.getRightVariable();
-                    substitutionVisitor.addSubstitution(substitutionVariableExpr.getVar(), reboundVariableExpr);
-                    UnnestClause newUnnestClause = new UnnestClause(copiedClause.getUnnestType(),
-                            copiedClause.getRightExpression(), new VariableExpr(reboundUnnestVariableID), null,
-                            copiedClause.getOuterUnnestMissingValueType());
-                    clauseSequence.addMainClause(newUnnestClause);
-                });
-            }
-        }
-
-        // If we have any LET clauses, we need to substitute them in our WHERE and SELECT clauses.
-        if (bodyAnalysisContext.getLetClauses() != null) {
-            for (LetClause letClause : bodyAnalysisContext.getLetClauses()) {
-                VarIdentifier reboundLetVariableID = graphixRewritingContext.getNewGraphixVariable();
-                VariableExpr reboundVariableExpr = new VariableExpr(reboundLetVariableID);
-
-                // Remap this LET-CLAUSE to include our new variables. Move this to our correlated clauses.
-                LetClause copiedClause = (LetClause) SqlppRewriteUtil.deepCopy(letClause);
-                copiedClause.accept(substitutionVisitor, null);
-                Expression copiedBindingExpr = copiedClause.getBindingExpr();
-                substitutionVisitor.addSubstitution(copiedClause.getVarExpr().getVar(), reboundVariableExpr);
-                loweringEnvironment.acceptTransformer(corrSequence -> {
-                    VariableExpr reboundLetVariableExpr = new VariableExpr(reboundLetVariableID);
-                    corrSequence.addMainClause(new CorrLetClause(copiedBindingExpr, reboundLetVariableExpr, null));
-                });
-            }
-        }
-
-        // If we have any WHERE clauses, we need to add these.
-        if (bodyAnalysisContext.getWhereClauses() != null) {
-            for (WhereClause whereClause : bodyAnalysisContext.getWhereClauses()) {
-                WhereClause copiedClause = (WhereClause) SqlppRewriteUtil.deepCopy(whereClause);
-                copiedClause.accept(substitutionVisitor, null);
-                loweringEnvironment.acceptTransformer(corrSequence -> {
-                    CorrWhereClause corrWhereClause = new CorrWhereClause(copiedClause.getWhereExpr());
-                    corrSequence.addMainClause(corrWhereClause);
-                });
-            }
-        }
-    }
-
-    protected RecordConstructor buildRecordConstructor() throws CompilationException {
-        if (bodyAnalysisContext.getSelectClauseProjections() != null) {
-            // Map our original variable to our element variable.
-            List<FieldBinding> fieldBindings = new ArrayList<>();
-            for (Projection projection : bodyAnalysisContext.getSelectClauseProjections()) {
-                LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(projection.getName()));
-                ILangExpression copiedExpr = SqlppRewriteUtil.deepCopy(projection.getExpression());
-                Expression fieldValueExpr = copiedExpr.accept(substitutionVisitor, null);
-                fieldBindings.add(new FieldBinding(fieldNameExpr, fieldValueExpr));
-            }
-            return new RecordConstructor(fieldBindings);
-
-        } else {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                    "Non-inlineable SELECT clause encountered, but was body was marked as inline!");
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java
deleted file mode 100644
index e54cc74..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower.action;
-
-import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.COMPLETE_ISOMORPHISM;
-import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.EDGE_ISOMORPHISM;
-import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.HOMOMORPHISM;
-import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.VERTEX_ISOMORPHISM;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringAliasLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
-import org.apache.asterix.graphix.lang.rewrites.lower.transform.CorrelatedClauseSequence;
-import org.apache.asterix.graphix.lang.rewrites.lower.transform.ISequenceTransformer;
-import org.apache.asterix.graphix.lang.rewrites.visitor.AbstractGraphixQueryVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.Clause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Define which graph elements are *not* equal to each other. We assume that all elements are named at this point and
- * that our {@link FromGraphClause} is in canonical form. We enforce the following (by default, we enforce total
- * isomorphism):
- * 1. No vertex and no edge can appear more than once across all patterns of all {@link MatchClause} nodes.
- * 2. For vertex-isomorphism, we enforce that no vertex can appear more than once across all {@link MatchClause} nodes.
- * 3. For edge-isomorphism, we enforce that no edge can appear more than once across all {@link MatchClause} nodes.
- * 4. For homomorphism, we enforce nothing. Edge adjacency is already implicitly preserved.
- */
-public class IsomorphismAction implements IEnvironmentAction {
-    public enum MatchEvaluationKind {
-        COMPLETE_ISOMORPHISM("isomorphism"),
-        VERTEX_ISOMORPHISM("vertex-isomorphism"),
-        EDGE_ISOMORPHISM("edge-isomorphism"),
-        HOMOMORPHISM("homomorphism");
-
-        // The user specifies the options above through "SET `graphix.match-evaluation` '...';"
-        private final String metadataConfigOptionName;
-
-        MatchEvaluationKind(String metadataConfigOptionName) {
-            this.metadataConfigOptionName = metadataConfigOptionName;
-        }
-
-        @Override
-        public String toString() {
-            return metadataConfigOptionName;
-        }
-    }
-
-    // We will walk through our FROM-GRAPH-CLAUSE and determine our isomorphism conjuncts.
-    private final MatchEvaluationKind matchEvaluationKind;
-    private final FromGraphClause fromGraphClause;
-    private final LoweringAliasLookupTable aliasLookupTable;
-    private final GraphixRewritingContext graphixRewritingContext;
-
-    public IsomorphismAction(GraphixRewritingContext graphixRewritingContext, FromGraphClause fromGraphClause,
-            LoweringAliasLookupTable aliasLookupTable) throws CompilationException {
-        final String metadataConfigKeyName = GraphixCompilationProvider.MATCH_EVALUATION_METADATA_CONFIG;
-        this.graphixRewritingContext = graphixRewritingContext;
-        this.fromGraphClause = fromGraphClause;
-        this.aliasLookupTable = aliasLookupTable;
-
-        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
-        if (metadataProvider.getConfig().containsKey(metadataConfigKeyName)) {
-            String metadataConfigKeyValue = (String) metadataProvider.getConfig().get(metadataConfigKeyName);
-            if (metadataConfigKeyValue.equalsIgnoreCase(COMPLETE_ISOMORPHISM.toString())) {
-                this.matchEvaluationKind = COMPLETE_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(VERTEX_ISOMORPHISM.toString())) {
-                this.matchEvaluationKind = VERTEX_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(EDGE_ISOMORPHISM.toString())) {
-                this.matchEvaluationKind = EDGE_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(HOMOMORPHISM.toString())) {
-                this.matchEvaluationKind = HOMOMORPHISM;
-
-            } else {
-                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, metadataConfigKeyValue);
-            }
-
-        } else {
-            this.matchEvaluationKind = COMPLETE_ISOMORPHISM;
-        }
-    }
-
-    private static List<OperatorExpr> generateIsomorphismConjuncts(List<VariableExpr> variableList) {
-        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
-
-        // Find all unique pairs from our list of variables.
-        for (int i = 0; i < variableList.size(); i++) {
-            for (int j = i + 1; j < variableList.size(); j++) {
-                OperatorExpr inequalityConjunct = new OperatorExpr();
-                inequalityConjunct.addOperator(OperatorType.NEQ);
-                inequalityConjunct.addOperand(variableList.get(i));
-                inequalityConjunct.addOperand(variableList.get(j));
-                isomorphismConjuncts.add(inequalityConjunct);
-            }
-        }
-
-        return isomorphismConjuncts;
-    }
-
-    @Override
-    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
-        Map<ElementLabel, List<VariableExpr>> vertexVariableMap = new HashMap<>();
-        Map<ElementLabel, List<VariableExpr>> edgeVariableMap = new HashMap<>();
-
-        // Populate the collections above.
-        fromGraphClause.accept(new AbstractGraphixQueryVisitor() {
-            private void populateVariableMap(ElementLabel label, VariableExpr variableExpr,
-                    Map<ElementLabel, List<VariableExpr>> labelVariableMap) {
-                Function<VariableExpr, Boolean> mapMatchFinder = v -> {
-                    final String variableName = SqlppVariableUtil.toUserDefinedName(variableExpr.getVar().getValue());
-                    String inputVariableName = SqlppVariableUtil.toUserDefinedName(v.getVar().getValue());
-                    return variableName.equals(inputVariableName);
-                };
-                if (labelVariableMap.containsKey(label)) {
-                    if (labelVariableMap.get(label).stream().noneMatch(mapMatchFinder::apply)) {
-                        labelVariableMap.get(label).add(variableExpr);
-                    }
-
-                } else {
-                    List<VariableExpr> variableList = new ArrayList<>();
-                    variableList.add(variableExpr);
-                    labelVariableMap.put(label, variableList);
-                }
-            }
-
-            @Override
-            public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-                // We only want to explore the top level of our FROM-GRAPH-CLAUSEs.
-                for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
-                    matchClause.accept(this, arg);
-                }
-                return null;
-            }
-
-            @Override
-            public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-                VarIdentifier vertexVariableID = vertexPatternExpr.getVariableExpr().getVar();
-                VarIdentifier iterationVariableID = aliasLookupTable.getIterationAlias(vertexVariableID);
-                ElementLabel elementLabel = vertexPatternExpr.getLabels().iterator().next();
-                populateVariableMap(elementLabel, new VariableExpr(iterationVariableID), vertexVariableMap);
-                return null;
-            }
-
-            @Override
-            public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) {
-                VarIdentifier edgeVariableID = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
-                VarIdentifier iterationVariableID = aliasLookupTable.getIterationAlias(edgeVariableID);
-                ElementLabel elementLabel = edgePatternExpr.getEdgeDescriptor().getEdgeLabels().iterator().next();
-                populateVariableMap(elementLabel, new VariableExpr(iterationVariableID), edgeVariableMap);
-                return null;
-            }
-        }, null);
-
-        // Construct our isomorphism conjuncts.
-        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
-        if (matchEvaluationKind == COMPLETE_ISOMORPHISM || matchEvaluationKind == VERTEX_ISOMORPHISM) {
-            vertexVariableMap.values().stream().map(IsomorphismAction::generateIsomorphismConjuncts)
-                    .forEach(isomorphismConjuncts::addAll);
-        }
-        if (matchEvaluationKind == COMPLETE_ISOMORPHISM || matchEvaluationKind == EDGE_ISOMORPHISM) {
-            edgeVariableMap.values().stream().map(IsomorphismAction::generateIsomorphismConjuncts)
-                    .forEach(isomorphismConjuncts::addAll);
-        }
-
-        // Iterate through our clause sequence, and introduce our isomorphism conjuncts eagerly.
-        VariableSubstitutionVisitor substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
-        loweringEnvironment.acceptTransformer(new ISequenceTransformer() {
-            private final Set<VarIdentifier> visitedVariables = new HashSet<>();
-
-            private void acceptLeftMatchJoin(ListIterator<AbstractBinaryCorrelateClause> clauseIterator,
-                    JoinClause joinClause) throws CompilationException {
-                // We can make the following assumptions about our JOIN here (i.e. the casts here are valid).
-                Expression rightExpression = joinClause.getRightExpression();
-                SelectExpression selectExpression = (SelectExpression) rightExpression;
-                SelectSetOperation selectSetOperation = selectExpression.getSelectSetOperation();
-                SelectBlock selectBlock = selectSetOperation.getLeftInput().getSelectBlock();
-                FromTerm fromTerm = selectBlock.getFromClause().getFromTerms().get(0);
-                for (AbstractBinaryCorrelateClause workingClause : fromTerm.getCorrelateClauses()) {
-                    if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
-                        continue;
-                    }
-                    VarIdentifier rightVariable = workingClause.getRightVariable().getVar();
-
-                    // Add our isomorphism conjunct to our main iterator.
-                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
-                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
-                        List<Expression> operandList = isomorphismConjunct.getExprList();
-                        VarIdentifier termVariable1 = ((VariableExpr) operandList.get(0)).getVar();
-                        VarIdentifier termVariable2 = ((VariableExpr) operandList.get(1)).getVar();
-                        if (!termVariable1.equals(rightVariable) && !termVariable2.equals(rightVariable)) {
-                            continue;
-                        }
-
-                        // Add a substitution for our right variable.
-                        VariableExpr nestingVariableExpr1 = new VariableExpr(joinClause.getRightVariable().getVar());
-                        VariableExpr nestingVariableExpr2 = new VariableExpr(joinClause.getRightVariable().getVar());
-                        FieldAccessor fieldAccessor1 = new FieldAccessor(nestingVariableExpr1, rightVariable);
-                        FieldAccessor fieldAccessor2 = new FieldAccessor(nestingVariableExpr2, rightVariable);
-                        substitutionVisitor.addSubstitution(rightVariable, fieldAccessor1);
-                        Expression qualifiedConjunct = substitutionVisitor.visit(isomorphismConjunct, null);
-
-                        // Our right variable can also be optional.
-                        FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.IS_MISSING);
-                        CallExpr isMissingCallExpr = new CallExpr(functionSignature, List.of(fieldAccessor2));
-                        OperatorExpr disjunctionExpr = new OperatorExpr();
-                        disjunctionExpr.addOperator(OperatorType.OR);
-                        disjunctionExpr.addOperand(isMissingCallExpr);
-                        disjunctionExpr.addOperand(qualifiedConjunct);
-                        clauseIterator.add(new CorrWhereClause(disjunctionExpr));
-                        appliedIsomorphismConjuncts.add(isomorphismConjunct);
-                        visitedVariables.add(rightVariable);
-                    }
-                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
-                }
-            }
-
-            @Override
-            public void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException {
-                ListIterator<AbstractBinaryCorrelateClause> clauseIterator = clauseSequence.getMainIterator();
-                while (clauseIterator.hasNext()) {
-                    AbstractBinaryCorrelateClause workingClause = clauseIterator.next();
-                    if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
-                        continue;
-                    }
-                    visitedVariables.add(workingClause.getRightVariable().getVar());
-
-                    // If we encounter a LEFT-JOIN, then we have created a LEFT-MATCH branch.
-                    if (workingClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
-                        JoinClause joinClause = (JoinClause) workingClause;
-                        if (joinClause.getJoinType() == JoinType.LEFTOUTER) {
-                            acceptLeftMatchJoin(clauseIterator, joinClause);
-                        }
-                    }
-
-                    // Only introduce our conjunct if we have visited both variables.
-                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
-                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
-                        List<Expression> operandList = isomorphismConjunct.getExprList();
-                        VarIdentifier termVariable1 = ((VariableExpr) operandList.get(0)).getVar();
-                        VarIdentifier termVariable2 = ((VariableExpr) operandList.get(1)).getVar();
-                        if (visitedVariables.contains(termVariable1) && visitedVariables.contains(termVariable2)) {
-                            clauseIterator.add(new CorrWhereClause(isomorphismConjunct));
-                            appliedIsomorphismConjuncts.add(isomorphismConjunct);
-                        }
-                    }
-                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
-                }
-            }
-        });
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java
deleted file mode 100644
index 3ea61d1..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.lower.transform;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-
-/**
- * A "general" list for maintaining a sequence of {@link AbstractBinaryCorrelateClause} AST nodes. We have:
- * 1. A list of main clauses, which our callers have free rein to manipulate.
- * 2. A list of deferred clauses, which will be inserted at the tail end of the final list.
- * 3. A list of vertex bindings, which will be inserted immediately after the main clause list.
- * 4. A list of edge bindings, which will be inserted immediately after the vertex binding list.
- */
-public class CorrelatedClauseSequence implements Iterable<AbstractBinaryCorrelateClause> {
-    private final LinkedList<AbstractBinaryCorrelateClause> deferredCorrelatedClauseSequence = new LinkedList<>();
-    private final LinkedList<AbstractBinaryCorrelateClause> mainCorrelatedClauseSequence = new LinkedList<>();
-    private final List<CorrLetClause> representativeVertexBindings = new ArrayList<>();
-    private final List<CorrLetClause> representativeEdgeBindings = new ArrayList<>();
-
-    public void addMainClause(AbstractBinaryCorrelateClause correlateClause) {
-        mainCorrelatedClauseSequence.addLast(correlateClause);
-    }
-
-    public void addDeferredClause(AbstractBinaryCorrelateClause correlateClause) {
-        deferredCorrelatedClauseSequence.add(correlateClause);
-    }
-
-    public void addRepresentativeVertexBinding(VarIdentifier representativeVariable, Expression bindingExpression) {
-        VariableExpr representativeVariableExpr = new VariableExpr(representativeVariable);
-        representativeVertexBindings.add(new CorrLetClause(bindingExpression, representativeVariableExpr, null));
-    }
-
-    public void addRepresentativeEdgeBinding(VarIdentifier representativeVariable, Expression bindingExpression) {
-        VariableExpr representativeVariableExpr = new VariableExpr(representativeVariable);
-        representativeEdgeBindings.add(new CorrLetClause(bindingExpression, representativeVariableExpr, null));
-    }
-
-    public AbstractBinaryCorrelateClause popMainClauseSequence() {
-        return mainCorrelatedClauseSequence.removeFirst();
-    }
-
-    public ListIterator<AbstractBinaryCorrelateClause> getMainIterator() {
-        return mainCorrelatedClauseSequence.listIterator();
-    }
-
-    public List<CorrLetClause> getRepresentativeVertexBindings() {
-        return representativeVertexBindings;
-    }
-
-    public List<CorrLetClause> getRepresentativeEdgeBindings() {
-        return representativeEdgeBindings;
-    }
-
-    @Override
-    public Iterator<AbstractBinaryCorrelateClause> iterator() {
-        List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<>();
-        correlateClauses.addAll(mainCorrelatedClauseSequence);
-        correlateClauses.addAll(representativeVertexBindings);
-        correlateClauses.addAll(representativeEdgeBindings);
-        correlateClauses.addAll(deferredCorrelatedClauseSequence);
-        return correlateClauses.listIterator();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java
deleted file mode 100644
index aa939fb..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.resolve;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.rewrites.visitor.StructureResolutionVisitor;
-
-/**
- * @see StructureResolutionVisitor
- */
-public interface IGraphElementResolver {
-    /**
-     * @param fromGraphClause FROM-GRAPH-CLAUSE to resolve edge & vertex labels, and edge directions for.
-     */
-    void resolve(FromGraphClause fromGraphClause) throws CompilationException;
-
-    /**
-     * @return True if we cannot resolve any more elements. False otherwise.
-     */
-    boolean isAtFixedPoint();
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
deleted file mode 100644
index 1d558fb..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.resolve;
-
-import java.util.Set;
-import java.util.function.BiFunction;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.LabelConsistencyVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.QueryKnowledgeVisitor;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-
-/**
- * Recursively attempt to resolve any element labels / edge directions in a FROM-GRAPH-CLAUSE.
- */
-public class InferenceBasedResolver implements IGraphElementResolver {
-    public static final String METADATA_CONFIG_NAME = "inference-based";
-
-    private final QueryKnowledgeVisitor queryKnowledgeVisitor;
-    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
-    private final SchemaKnowledgeTable schemaKnowledgeTable;
-    private boolean isAtFixedPoint = false;
-
-    public InferenceBasedResolver(SchemaKnowledgeTable schemaKnowledgeTable) {
-        this.queryKnowledgeVisitor = new QueryKnowledgeVisitor();
-        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
-        this.schemaKnowledgeTable = schemaKnowledgeTable;
-    }
-
-    @Override
-    public void resolve(FromGraphClause fromGraphClause) throws CompilationException {
-        isAtFixedPoint = true;
-
-        // Update our query knowledge, then unify vertices across our FROM-GRAPH-CLAUSE.
-        queryKnowledgeVisitor.visit(fromGraphClause, null);
-        new LabelConsistencyVisitor(queryKnowledgeVisitor.getQueryKnowledgeTable()).visit(fromGraphClause, null);
-
-        // Perform our resolution.
-        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
-            for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
-                // We can only infer labels on edges. We ignore dangling vertices.
-                for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
-                    isAtFixedPoint &= resolveEdge(edgeExpression);
-                }
-            }
-        }
-    }
-
-    /**
-     * Attempt to resolve the unknowns in an {@link EdgePatternExpr}. This includes the labels of the contained
-     * vertices and edges, as well as the edge direction.
-     * @return True if no new information was added. False otherwise.
-     */
-    private boolean resolveEdge(EdgePatternExpr edgePatternExpr) throws CompilationException {
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH) {
-            VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
-
-            // We have a sub-path. Recurse with the edges of this sub-path.
-            boolean intermediateResult = true;
-            for (int i = 0; i < edgeDescriptor.getMaximumHops(); i++) {
-                VertexPatternExpr rightVertex;
-                if (i == edgeDescriptor.getMaximumHops() - 1) {
-                    // This is the final vertex in our path.
-                    rightVertex = edgePatternExpr.getRightVertex();
-
-                } else {
-                    // We need to get an intermediate vertex.
-                    rightVertex = edgePatternExpr.getInternalVertices().get(i);
-                }
-
-                // Build our EDGE-PATTERN-EXPR and recurse.
-                EdgeDescriptor newDescriptor = new EdgeDescriptor(edgeDescriptor.getEdgeDirection(),
-                        EdgeDescriptor.PatternType.EDGE, edgeDescriptor.getEdgeLabels(), null, null, null);
-                intermediateResult &= resolveEdge(new EdgePatternExpr(workingLeftVertex, rightVertex, newDescriptor));
-
-                // Update the labels of our edge and our internal vertex.
-                edgeDescriptor.getEdgeLabels().addAll(newDescriptor.getEdgeLabels());
-                if (i != edgeDescriptor.getMaximumHops() - 1) {
-                    for (ElementLabel label : rightVertex.getLabels()) {
-                        // Mark our labels as not inferred to prevent invalidation of what we just found.
-                        label.markInferred(false);
-                    }
-                }
-                workingLeftVertex = rightVertex;
-            }
-
-            return intermediateResult;
-        }
-
-        if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED) {
-            // We have an undirected edge. Recurse with a LEFT_TO_RIGHT edge...
-            EdgePatternExpr leftToRightEdgePatternExpr = graphixDeepCopyVisitor.visit(edgePatternExpr, null);
-            leftToRightEdgePatternExpr.getEdgeDescriptor().setEdgeDirection(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT);
-            boolean isLeftToRightModified = !resolveEdge(leftToRightEdgePatternExpr);
-
-            // ...and a RIGHT_TO_LEFT edge.
-            EdgePatternExpr rightToLeftEdgePatternExpr = graphixDeepCopyVisitor.visit(edgePatternExpr, null);
-            rightToLeftEdgePatternExpr.getEdgeDescriptor().setEdgeDirection(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT);
-            boolean isRightToLeftModified = !resolveEdge(rightToLeftEdgePatternExpr);
-
-            // Determine the direction of our edge, if possible.
-            if (isLeftToRightModified && !isRightToLeftModified) {
-                edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT);
-
-            } else if (!isLeftToRightModified && isRightToLeftModified) {
-                edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT);
-
-            } else {
-                edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.UNDIRECTED);
-            }
-
-            // Propagate our label sets.
-            VertexPatternExpr leftVertexExpr = edgePatternExpr.getLeftVertex();
-            VertexPatternExpr rightVertexExpr = edgePatternExpr.getRightVertex();
-            if (isLeftToRightModified) {
-                edgeDescriptor.getEdgeLabels().addAll(leftToRightEdgePatternExpr.getEdgeDescriptor().getEdgeLabels());
-                leftVertexExpr.getLabels().addAll(leftToRightEdgePatternExpr.getLeftVertex().getLabels());
-                rightVertexExpr.getLabels().addAll(leftToRightEdgePatternExpr.getRightVertex().getLabels());
-            }
-            if (isRightToLeftModified) {
-                edgeDescriptor.getEdgeLabels().addAll(rightToLeftEdgePatternExpr.getEdgeDescriptor().getEdgeLabels());
-                leftVertexExpr.getLabels().addAll(rightToLeftEdgePatternExpr.getLeftVertex().getLabels());
-                rightVertexExpr.getLabels().addAll(rightToLeftEdgePatternExpr.getRightVertex().getLabels());
-            }
-            return !(isLeftToRightModified || isRightToLeftModified);
-        }
-
-        // We have a _directed_ *edge*. Determine our source and destination vertex.
-        VertexPatternExpr sourceVertexPattern, destinationVertexPattern;
-        if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
-            sourceVertexPattern = edgePatternExpr.getLeftVertex();
-            destinationVertexPattern = edgePatternExpr.getRightVertex();
-
-        } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
-            sourceVertexPattern = edgePatternExpr.getRightVertex();
-            destinationVertexPattern = edgePatternExpr.getLeftVertex();
-        }
-
-        // An unknown is valid iff the element is not contained within the label set and **every** label is inferred.
-        final BiFunction<Set<ElementLabel>, ElementLabel, Boolean> validUnknownPredicate =
-                (s, e) -> !s.contains(e) && s.stream().allMatch(ElementLabel::isInferred);
-        Set<ElementLabel> sourceLabels = sourceVertexPattern.getLabels();
-        Set<ElementLabel> destLabels = destinationVertexPattern.getLabels();
-        Set<ElementLabel> edgeLabels = edgeDescriptor.getEdgeLabels();
-
-        // Iterate through our knowledge table. Attempt to fill in unknowns.
-        boolean isUnknownFilled = false;
-        for (SchemaKnowledgeTable.KnowledgeRecord knowledgeRecord : schemaKnowledgeTable) {
-            ElementLabel recordSourceLabel = knowledgeRecord.getSourceVertexLabel();
-            ElementLabel recordDestLabel = knowledgeRecord.getDestVertexLabel();
-            ElementLabel recordEdgeLabel = knowledgeRecord.getEdgeLabel();
-            boolean isSourceValidUnknown = validUnknownPredicate.apply(sourceLabels, recordSourceLabel);
-            boolean isDestValidUnknown = validUnknownPredicate.apply(destLabels, recordDestLabel);
-            boolean isEdgeValidUnknown = validUnknownPredicate.apply(edgeLabels, recordEdgeLabel);
-            if (sourceLabels.contains(recordSourceLabel)) {
-                if (edgeLabels.contains(recordEdgeLabel) && isDestValidUnknown) {
-                    // Source is known, edge is known, dest is unknown.
-                    destLabels.add(recordDestLabel.asInferred());
-                    isUnknownFilled = true;
-
-                } else if (isEdgeValidUnknown && destLabels.contains(recordDestLabel)) {
-                    // Source is known, edge is unknown, dest is known.
-                    edgeLabels.add(recordEdgeLabel.asInferred());
-                    isUnknownFilled = true;
-
-                } else if (isEdgeValidUnknown && isDestValidUnknown) {
-                    // Source is known, edge is unknown, dest is unknown.
-                    destLabels.add(recordDestLabel.asInferred());
-                    edgeLabels.add(recordEdgeLabel.asInferred());
-                    isUnknownFilled = true;
-                }
-
-            } else if (edgeLabels.contains(recordEdgeLabel)) {
-                if (isSourceValidUnknown && destLabels.contains(recordDestLabel)) {
-                    // Source is unknown, edge is known, dest is known.
-                    sourceLabels.add(recordSourceLabel.asInferred());
-                    isUnknownFilled = true;
-
-                } else if (isSourceValidUnknown && isDestValidUnknown) {
-                    // Source is unknown, edge is known, dest is unknown.
-                    sourceLabels.add(recordSourceLabel.asInferred());
-                    destLabels.add(recordDestLabel.asInferred());
-                    isUnknownFilled = true;
-                }
-
-            } else if (destLabels.contains(recordDestLabel) && isSourceValidUnknown && isEdgeValidUnknown) {
-                // Source is unknown, edge is unknown, dest is known.
-                sourceLabels.add(recordSourceLabel.asInferred());
-                edgeLabels.add(recordEdgeLabel.asInferred());
-                isUnknownFilled = true;
-            }
-        }
-        return !isUnknownFilled;
-    }
-
-    @Override
-    public boolean isAtFixedPoint() {
-        return isAtFixedPoint;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/QueryKnowledgeTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/QueryKnowledgeTable.java
deleted file mode 100644
index 23b54e4..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/QueryKnowledgeTable.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.resolve;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.struct.Identifier;
-
-/**
- * A knowledge base of our query graph, stored as a map of vertex variable IDs to a list of vertex labels.
- *
- * @see InferenceBasedResolver
- */
-public class QueryKnowledgeTable extends HashMap<Identifier, List<ElementLabel>> {
-    private static final long serialVersionUID = 1L;
-
-    public void put(VertexPatternExpr vertexPatternExpr) {
-        Identifier vertexIdentifier = vertexPatternExpr.getVariableExpr().getVar();
-        if (!vertexPatternExpr.getLabels().isEmpty()) {
-            if (!super.containsKey(vertexIdentifier)) {
-                super.put(vertexIdentifier, new ArrayList<>());
-            }
-            super.get(vertexIdentifier).addAll(vertexPatternExpr.getLabels());
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java
deleted file mode 100644
index 1b2984d..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.resolve;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Objects;
-import java.util.Set;
-
-import org.apache.asterix.graphix.lang.expression.GraphConstructor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.graphix.metadata.entity.schema.Graph;
-import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
-
-/**
- * A collection of ground truths, derived from the graph schema (either a {@link Graph} or {@link GraphConstructor}).
- *
- * @see InferenceBasedResolver
- */
-public class SchemaKnowledgeTable implements Iterable<SchemaKnowledgeTable.KnowledgeRecord> {
-    private final Set<KnowledgeRecord> knowledgeRecordSet = new HashSet<>();
-    private final Set<ElementLabel> vertexLabelSet = new HashSet<>();
-    private final Set<ElementLabel> edgeLabelSet = new HashSet<>();
-
-    public SchemaKnowledgeTable(Graph graph) {
-        graph.getGraphSchema().getVertices().stream().map(Vertex::getLabel).forEach(vertexLabelSet::add);
-        graph.getGraphSchema().getEdges().forEach(e -> {
-            vertexLabelSet.add(e.getSourceLabel());
-            vertexLabelSet.add(e.getDestinationLabel());
-            edgeLabelSet.add(e.getLabel());
-            knowledgeRecordSet.add(new KnowledgeRecord(e.getSourceLabel(), e.getDestinationLabel(), e.getLabel()));
-        });
-    }
-
-    public SchemaKnowledgeTable(GraphConstructor graphConstructor) {
-        graphConstructor.getVertexElements().forEach(v -> vertexLabelSet.add(v.getLabel()));
-        graphConstructor.getEdgeElements().forEach(e -> {
-            vertexLabelSet.add(e.getSourceLabel());
-            vertexLabelSet.add(e.getDestinationLabel());
-            edgeLabelSet.add(e.getEdgeLabel());
-            knowledgeRecordSet.add(new KnowledgeRecord(e.getSourceLabel(), e.getDestinationLabel(), e.getEdgeLabel()));
-        });
-    }
-
-    public Set<ElementLabel> getVertexLabelSet() {
-        return vertexLabelSet;
-    }
-
-    public Set<ElementLabel> getEdgeLabelSet() {
-        return edgeLabelSet;
-    }
-
-    @Override
-    public Iterator<KnowledgeRecord> iterator() {
-        return knowledgeRecordSet.iterator();
-    }
-
-    public static class KnowledgeRecord {
-        private final ElementLabel sourceVertexLabel;
-        private final ElementLabel destVertexLabel;
-        private final ElementLabel edgeLabel;
-
-        public KnowledgeRecord(ElementLabel sourceVertexLabel, ElementLabel destVertexLabel, ElementLabel edgeLabel) {
-            this.sourceVertexLabel = Objects.requireNonNull(sourceVertexLabel);
-            this.destVertexLabel = Objects.requireNonNull(destVertexLabel);
-            this.edgeLabel = Objects.requireNonNull(edgeLabel);
-        }
-
-        public ElementLabel getSourceVertexLabel() {
-            return sourceVertexLabel;
-        }
-
-        public ElementLabel getDestVertexLabel() {
-            return destVertexLabel;
-        }
-
-        public ElementLabel getEdgeLabel() {
-            return edgeLabel;
-        }
-
-        @Override
-        public String toString() {
-            return String.format("(%s)-[%s]->(%s)", sourceVertexLabel, edgeLabel, destVertexLabel);
-        }
-
-        @Override
-        public boolean equals(Object object) {
-            if (this == object) {
-                return true;
-            }
-            if (!(object instanceof KnowledgeRecord)) {
-                return false;
-            }
-            KnowledgeRecord target = (KnowledgeRecord) object;
-            return sourceVertexLabel.equals(target.sourceVertexLabel) && destVertexLabel.equals(target.destVertexLabel)
-                    && edgeLabel.equals(target.edgeLabel);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(sourceVertexLabel, destVertexLabel, edgeLabel);
-        }
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java
deleted file mode 100644
index b7b1b33..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.canonical.DanglingVertexExpander;
-import org.apache.asterix.graphix.lang.rewrites.canonical.EdgeSubPathExpander;
-import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
-import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
-import org.apache.asterix.graphix.metadata.entity.schema.Graph;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Clause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.SetOpType;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-
-/**
- * Expand a single {@link SelectSetOperation} with {@link VertexPatternExpr} and {@link EdgePatternExpr} nodes into
- * several UNION-ALL branches of "canonical form". This preprocessing step allows for simpler lowering logic (and
- * therefore, potentially more efficient plans) at the cost of a wider AST.
- *
- * @see DanglingVertexExpander
- * @see EdgeSubPathExpander
- */
-public class CanonicalExpansionVisitor extends AbstractGraphixQueryVisitor {
-    private final GraphixRewritingContext graphixRewritingContext;
-    private final Deque<CanonicalPatternEnvironment> environmentStack;
-    private final DanglingVertexExpander danglingVertexExpander;
-    private final EdgeSubPathExpander edgeSubPathExpander;
-
-    // To avoid "over-expanding", we require knowledge of our schema.
-    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
-    private final MetadataProvider metadataProvider;
-
-    private static class CanonicalPatternEnvironment {
-        private List<GraphSelectBlock> graphSelectBlockList;
-        private List<SetOperationInput> setOperationInputList;
-        private SchemaKnowledgeTable schemaKnowledgeTable;
-        private boolean wasExpanded = false;
-
-        // The following is collected for our post-canonicalization pass.
-        private GraphSelectBlock selectBlockExpansionSource;
-        private Set<SetOperationInput> generatedSetInputs;
-        private List<VarIdentifier> userLiveVariables;
-    }
-
-    public CanonicalExpansionVisitor(GraphixRewritingContext graphixRewritingContext) {
-        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
-        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
-        this.danglingVertexExpander = new DanglingVertexExpander();
-        this.edgeSubPathExpander = new EdgeSubPathExpander(graphixRewritingContext::getNewGraphixVariable);
-        this.graphixRewritingContext = graphixRewritingContext;
-
-        // Keep an empty environment in the stack, in case we have a non-SELECT expression.
-        this.environmentStack = new ArrayDeque<>();
-        this.environmentStack.addLast(new CanonicalPatternEnvironment());
-    }
-
-    @Override
-    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        environmentStack.addLast(new CanonicalPatternEnvironment());
-        super.visit(selectExpression, arg);
-
-        // If expansion has occurred, we need to perform clean-up if we have output modifiers / grouping.
-        CanonicalPatternEnvironment workingEnvironment = environmentStack.removeLast();
-        SelectExpression workingSelectExpression = selectExpression;
-        if (workingEnvironment.wasExpanded && (selectExpression.hasLimit() || selectExpression.hasOrderby()
-                || workingEnvironment.graphSelectBlockList.stream().anyMatch(SelectBlock::hasGroupbyClause))) {
-            PostCanonicalizationVisitor postCanonicalizationVisitor = new PostCanonicalizationVisitor(
-                    graphixRewritingContext, workingEnvironment.selectBlockExpansionSource,
-                    workingEnvironment.generatedSetInputs, workingEnvironment.userLiveVariables);
-            workingSelectExpression = (SelectExpression) postCanonicalizationVisitor.visit(selectExpression, arg);
-        }
-        return workingSelectExpression;
-    }
-
-    @Override
-    public Expression visit(SelectSetOperation selectSetOperation, ILangExpression arg) throws CompilationException {
-        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
-        workingEnvironment.setOperationInputList = new ArrayList<>();
-        selectSetOperation.getLeftInput().accept(this, arg);
-        for (SetOperationRight right : selectSetOperation.getRightInputs()) {
-            right.getSetOperationRightInput().accept(this, arg);
-        }
-        for (int i = 0; i < workingEnvironment.setOperationInputList.size(); i++) {
-            SetOperationInput setOperationInput = workingEnvironment.setOperationInputList.get(i);
-            if (i == 0) {
-                selectSetOperation.getLeftInput().setSelectBlock(setOperationInput.getSelectBlock());
-                selectSetOperation.getLeftInput().setSubquery(setOperationInput.getSubquery());
-
-            } else if (selectSetOperation.getRightInputs().size() > i) {
-                SetOperationInput setOperationRightInput =
-                        selectSetOperation.getRightInputs().get(i - 1).getSetOperationRightInput();
-                setOperationRightInput.setSelectBlock(setOperationInput.getSelectBlock());
-                setOperationRightInput.setSubquery(setOperationRightInput.getSubquery());
-
-            } else {
-                SetOperationRight setOperationRight = new SetOperationRight(SetOpType.UNION, false, setOperationInput);
-                selectSetOperation.getRightInputs().add(setOperationRight);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
-        workingEnvironment.graphSelectBlockList = new ArrayList<>();
-        workingEnvironment.graphSelectBlockList.add(graphSelectBlock);
-        super.visit(graphSelectBlock, arg);
-
-        if (workingEnvironment.wasExpanded) {
-            // If we have expanded, then we need to keep track of this GRAPH-SELECT-BLOCK.
-            workingEnvironment.selectBlockExpansionSource = new GraphixDeepCopyVisitor().visit(graphSelectBlock, null);
-
-            // We also need to collect all live variables up to this point.
-            workingEnvironment.userLiveVariables = new ArrayList<>();
-            for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
-                for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
-                    if (pathExpression.getVariableExpr() != null) {
-                        workingEnvironment.userLiveVariables.add(pathExpression.getVariableExpr().getVar());
-                    }
-                    for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
-                        VarIdentifier vertexVariable = vertexExpression.getVariableExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(vertexVariable)) {
-                            workingEnvironment.userLiveVariables.add(vertexVariable);
-                        }
-                    }
-                    for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
-                        VarIdentifier edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(edgeVariable)) {
-                            workingEnvironment.userLiveVariables.add(edgeVariable);
-                        }
-                    }
-                }
-            }
-            if (!graphSelectBlock.getFromGraphClause().getCorrelateClauses().isEmpty()) {
-                FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
-                List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
-                for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
-                    VarIdentifier bindingVariable = correlateClause.getRightVariable().getVar();
-                    if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
-                        workingEnvironment.userLiveVariables.add(bindingVariable);
-                    }
-                }
-            }
-            if (graphSelectBlock.hasLetWhereClauses()) {
-                for (AbstractClause abstractClause : graphSelectBlock.getLetWhereList()) {
-                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
-                        LetClause letClause = (LetClause) abstractClause;
-                        VarIdentifier bindingVariable = letClause.getVarExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
-                            workingEnvironment.userLiveVariables.add(bindingVariable);
-                        }
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
-
-        // Establish our schema knowledge.
-        if (fromGraphClause.getGraphConstructor() == null) {
-            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
-                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
-            Identifier graphName = fromGraphClause.getGraphName();
-
-            // First, try to find our graph inside our declared graph set.
-            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
-            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
-            if (declaredGraph != null) {
-                workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(declaredGraph.getGraphConstructor());
-
-            } else {
-                // Otherwise, fetch the graph from our metadata.
-                try {
-                    Graph graphFromMetadata =
-                            getGraph(metadataProvider.getMetadataTxnContext(), dataverseName, graphName.getValue());
-                    if (graphFromMetadata == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                                "Graph " + graphName.getValue() + " does not exist.");
-                    }
-                    workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
-
-                } catch (AlgebricksException e) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                            "Graph " + graphName.getValue() + " does not exist.");
-                }
-            }
-
-        } else {
-            workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(fromGraphClause.getGraphConstructor());
-        }
-        super.visit(fromGraphClause, arg);
-
-        // Create SOI from our GSBs back to our immediate ancestor SELECT-EXPR.
-        if (workingEnvironment.graphSelectBlockList.size() > 1) {
-            workingEnvironment.generatedSetInputs = new HashSet<>();
-            workingEnvironment.wasExpanded = true;
-        }
-        for (GraphSelectBlock graphSelectBlock : workingEnvironment.graphSelectBlockList) {
-            SetOperationInput setOperationInput = new SetOperationInput(graphSelectBlock, null);
-            workingEnvironment.setOperationInputList.add(setOperationInput);
-            if (workingEnvironment.wasExpanded) {
-                workingEnvironment.generatedSetInputs.add(setOperationInput);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg) throws CompilationException {
-        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
-        Set<VertexPatternExpr> connectedVertices = new HashSet<>();
-        for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
-            connectedVertices.add(edgeExpression.getLeftVertex());
-            connectedVertices.add(edgeExpression.getRightVertex());
-            edgeSubPathExpander.resetSchema(workingEnvironment.schemaKnowledgeTable);
-            edgeSubPathExpander.apply(edgeExpression, workingEnvironment.graphSelectBlockList);
-        }
-        for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
-            if (!connectedVertices.contains(vertexExpression)) {
-                danglingVertexExpander.apply(vertexExpression, workingEnvironment.graphSelectBlockList);
-            }
-        }
-        return pathPatternExpr;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
deleted file mode 100644
index 0b55145..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Function;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.EnvironmentActionFactory;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringAliasLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
-import org.apache.asterix.graphix.lang.rewrites.visitor.StructureAnalysisVisitor.StructureContext;
-import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-
-/**
- * Rewrite a graph AST to utilize non-graph AST nodes (i.e. replace GRAPH-SELECT-BLOCKs with a SELECT-BLOCK).
- */
-public class GraphixLoweringVisitor extends AbstractGraphixQueryVisitor {
-    private final Map<FromGraphClause, StructureContext> fromGraphClauseContextMap;
-    private final ElementLookupTable elementLookupTable;
-    private final GraphixRewritingContext graphixRewritingContext;
-    private SelectExpression topLevelSelectExpression;
-
-    // Our stack corresponds to which GRAPH-SELECT-BLOCK we are currently working with.
-    private final Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
-    private final Deque<LoweringEnvironment> environmentStack;
-    private final LoweringAliasLookupTable aliasLookupTable;
-    private final EnvironmentActionFactory environmentActionFactory;
-
-    public GraphixLoweringVisitor(GraphixRewritingContext graphixRewritingContext,
-            ElementLookupTable elementLookupTable, Map<FromGraphClause, StructureContext> fromGraphClauseContextMap) {
-        this.fromGraphClauseContextMap = Objects.requireNonNull(fromGraphClauseContextMap);
-        this.elementLookupTable = Objects.requireNonNull(elementLookupTable);
-        this.graphixRewritingContext = Objects.requireNonNull(graphixRewritingContext);
-        this.aliasLookupTable = new LoweringAliasLookupTable();
-        this.environmentStack = new ArrayDeque<>();
-
-        // All actions on our environment are supplied by the factory below.
-        Map<GraphElementIdentifier, ElementBodyAnalysisContext> bodyAnalysisContextMap = new HashMap<>();
-        this.environmentActionFactory = new EnvironmentActionFactory(bodyAnalysisContextMap, elementLookupTable,
-                aliasLookupTable, graphixRewritingContext);
-        this.analysisContextMap = bodyAnalysisContextMap;
-    }
-
-    @Override
-    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        if (!selectExpression.isSubquery() || topLevelSelectExpression == null) {
-            topLevelSelectExpression = selectExpression;
-        }
-        return super.visit(selectExpression, arg);
-    }
-
-    @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        SelectExpression selectExpression = (SelectExpression) arg;
-        if (graphSelectBlock.hasFromGraphClause()) {
-            FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
-
-            // Initialize a new lowering environment.
-            GraphIdentifier graphIdentifier = fromGraphClauseContextMap.get(fromGraphClause).getGraphIdentifier();
-            LoweringEnvironment newEnvironment = new LoweringEnvironment(graphSelectBlock, graphixRewritingContext);
-            environmentActionFactory.reset(graphIdentifier);
-
-            // We will remove the FROM-GRAPH node and replace this with a FROM node on the child visit.
-            environmentStack.addLast(newEnvironment);
-            super.visit(graphSelectBlock, graphSelectBlock);
-            environmentStack.removeLast();
-
-            // See if there are Graphix functions declared anywhere in our query.
-            Set<FunctionIdentifier> graphixFunctionSet = new HashSet<>();
-            topLevelSelectExpression.accept(new AbstractGraphixQueryVisitor() {
-                @Override
-                public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
-                    FunctionSignature functionSignature = callExpr.getFunctionSignature();
-                    if (functionSignature.getDataverseName().equals(GraphixFunctionIdentifiers.GRAPHIX_DV)) {
-                        graphixFunctionSet.add(functionSignature.createFunctionIdentifier());
-                    }
-                    return super.visit(callExpr, arg);
-                }
-            }, null);
-
-            // If so, then we need to perform a pass for schema enrichment.
-            if (!graphixFunctionSet.isEmpty()) {
-                SchemaEnrichmentVisitor schemaEnrichmentVisitor = new SchemaEnrichmentVisitor(elementLookupTable,
-                        graphIdentifier, graphSelectBlock, graphixFunctionSet);
-                selectExpression.accept(schemaEnrichmentVisitor, null);
-                if (selectExpression.hasOrderby()) {
-                    selectExpression.getOrderbyClause().accept(schemaEnrichmentVisitor, null);
-                }
-                if (selectExpression.hasLimit()) {
-                    selectExpression.getLimitClause().accept(schemaEnrichmentVisitor, null);
-                }
-            }
-
-        } else {
-            super.visit(graphSelectBlock, arg);
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-        // Perform an analysis pass over each element body. We need to determine what we can and can't inline.
-        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
-            ElementBodyAnalysisVisitor elementBodyAnalysisVisitor = new ElementBodyAnalysisVisitor();
-            GraphElementIdentifier elementIdentifier = graphElementDeclaration.getIdentifier();
-            graphElementDeclaration.getNormalizedBody().accept(elementBodyAnalysisVisitor, null);
-            analysisContextMap.put(elementIdentifier, elementBodyAnalysisVisitor.getElementBodyAnalysisContext());
-        }
-        LoweringEnvironment workingEnvironment = environmentStack.getLast();
-
-        // Lower our MATCH-CLAUSEs. We should be working with canonical-ized patterns.
-        boolean wasInitialEdgeOrderingEncountered = false;
-        StructureContext structureContext = fromGraphClauseContextMap.get(fromGraphClause);
-        Deque<List<VertexPatternExpr>> danglingVertexQueue = structureContext.getDanglingVertexQueue();
-        Deque<List<PathPatternExpr>> pathPatternQueue = structureContext.getPathPatternQueue();
-        for (Iterable<EdgePatternExpr> edgeOrdering : structureContext.getEdgeDependencyGraph()) {
-            if (wasInitialEdgeOrderingEncountered) {
-                workingEnvironment.beginLeftMatch();
-            }
-            for (EdgePatternExpr edgePatternExpr : edgeOrdering) {
-                edgePatternExpr.accept(this, fromGraphClause);
-            }
-            for (VertexPatternExpr danglingVertexExpr : danglingVertexQueue.removeFirst()) {
-                workingEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(danglingVertexExpr));
-            }
-            if (wasInitialEdgeOrderingEncountered) {
-                workingEnvironment.endLeftMatch(graphixRewritingContext.getWarningCollector());
-            }
-            for (PathPatternExpr pathPatternExpr : pathPatternQueue.removeFirst()) {
-                workingEnvironment.acceptAction(environmentActionFactory.buildPathPatternAction(pathPatternExpr));
-            }
-            wasInitialEdgeOrderingEncountered = true;
-        }
-        if (!structureContext.getEdgeDependencyGraph().iterator().hasNext()) {
-            for (VertexPatternExpr danglingVertexExpr : danglingVertexQueue.removeFirst()) {
-                workingEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(danglingVertexExpr));
-            }
-            for (PathPatternExpr pathPatternExpr : pathPatternQueue.removeFirst()) {
-                workingEnvironment.acceptAction(environmentActionFactory.buildPathPatternAction(pathPatternExpr));
-            }
-        }
-        workingEnvironment.acceptAction(environmentActionFactory.buildIsomorphismAction(fromGraphClause));
-
-        // Finalize our lowering by removing this FROM-GRAPH-CLAUSE from our parent GRAPH-SELECT-BLOCK.
-        workingEnvironment.finalizeLowering(fromGraphClause, graphixRewritingContext.getWarningCollector());
-
-        // Add our correlate clauses, if any, to our tail FROM-TERM.
-        if (!fromGraphClause.getCorrelateClauses().isEmpty()) {
-            GraphSelectBlock graphSelectBlock = (GraphSelectBlock) arg;
-            List<FromTerm> fromTerms = graphSelectBlock.getFromClause().getFromTerms();
-            FromTerm tailFromTerm = fromTerms.get(fromTerms.size() - 1);
-            tailFromTerm.getCorrelateClauses().addAll(fromGraphClause.getCorrelateClauses());
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        LoweringEnvironment lowerEnvironment = environmentStack.getLast();
-
-        // We should only be working with one identifier (given that we only have one label).
-        GraphIdentifier graphIdentifier = fromGraphClauseContextMap.get((FromGraphClause) arg).getGraphIdentifier();
-        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        if (edgeElementIDs.size() != 1) {
-            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
-        }
-        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
-        ElementBodyAnalysisContext edgeBodyAnalysisContext = analysisContextMap.get(edgeIdentifier);
-        DataverseName edgeDataverseName = edgeBodyAnalysisContext.getDataverseName();
-        String edgeDatasetName = edgeBodyAnalysisContext.getDatasetName();
-        boolean isEdgeInline = edgeBodyAnalysisContext.isExpressionInline();
-
-        // Determine our source and destination vertices.
-        VertexPatternExpr sourceVertex, destVertex;
-        if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
-            sourceVertex = edgePatternExpr.getLeftVertex();
-            destVertex = edgePatternExpr.getRightVertex();
-
-        } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
-            sourceVertex = edgePatternExpr.getRightVertex();
-            destVertex = edgePatternExpr.getLeftVertex();
-        }
-
-        // Collect information about our source -> edge JOIN.
-        GraphElementIdentifier sourceIdentifier = sourceVertex.generateIdentifiers(graphIdentifier).get(0);
-        ElementBodyAnalysisContext sourceBodyAnalysisContext = analysisContextMap.get(sourceIdentifier);
-        VarIdentifier sourceVertexVariable = sourceVertex.getVariableExpr().getVar();
-        List<List<String>> sourceVertexKey = elementLookupTable.getVertexKey(sourceIdentifier);
-        List<List<String>> sourceEdgeKey = elementLookupTable.getEdgeSourceKey(edgeIdentifier);
-        Function<GraphElementIdentifier, List<List<String>>> sourceKey = elementLookupTable::getEdgeSourceKey;
-        boolean isSourceInline = sourceBodyAnalysisContext.isExpressionInline();
-        boolean isSourceIntroduced = aliasLookupTable.getIterationAlias(sourceVertexVariable) != null;
-        boolean isSourceFolded = isSourceInline && sourceBodyAnalysisContext.getDatasetName().equals(edgeDatasetName)
-                && sourceBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
-                && sourceVertexKey.equals(sourceEdgeKey);
-
-        // ...and our dest -> edge JOIN.
-        GraphElementIdentifier destIdentifier = destVertex.generateIdentifiers(graphIdentifier).get(0);
-        ElementBodyAnalysisContext destBodyAnalysisContext = analysisContextMap.get(destIdentifier);
-        VarIdentifier destVertexVariable = destVertex.getVariableExpr().getVar();
-        List<List<String>> destVertexKey = elementLookupTable.getVertexKey(destIdentifier);
-        List<List<String>> destEdgeKey = elementLookupTable.getEdgeDestKey(edgeIdentifier);
-        Function<GraphElementIdentifier, List<List<String>>> destKey = elementLookupTable::getEdgeDestKey;
-        boolean isDestInline = destBodyAnalysisContext.isExpressionInline();
-        boolean isDestIntroduced = aliasLookupTable.getIterationAlias(destVertexVariable) != null;
-        boolean isDestFolded = isDestInline && destBodyAnalysisContext.getDatasetName().equals(edgeDatasetName)
-                && destBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
-                && destVertexKey.equals(destEdgeKey);
-
-        // Determine our strategy for lowering our edge.
-        if (isEdgeInline && isSourceFolded && !isDestIntroduced) {
-            if (!isSourceIntroduced) {
-                lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(sourceVertex));
-            }
-            lowerEnvironment
-                    .acceptAction(environmentActionFactory.buildFoldedEdgeAction(sourceVertex, edgePatternExpr));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey));
-
-        } else if (isEdgeInline && isDestFolded && !isSourceIntroduced) {
-            if (!isDestIntroduced) {
-                lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(destVertex));
-            }
-            lowerEnvironment.acceptAction(environmentActionFactory.buildFoldedEdgeAction(destVertex, edgePatternExpr));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey));
-
-        } else if (isSourceIntroduced && isDestIntroduced) {
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildRawJoinVertexAction(destVertex, edgePatternExpr, destKey));
-
-        } else if (isSourceIntroduced) { // !isDestIntroduced
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey));
-
-        } else if (isDestIntroduced) { // !isSourceIntroduced
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildNonFoldedEdgeAction(destVertex, edgePatternExpr, destKey));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey));
-
-        } else { // !isSourceIntroduced && !isDestIntroduced
-            // When nothing is introduced, start off from LEFT to RIGHT instead of considering our source and dest.
-            VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
-            VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
-            Function<GraphElementIdentifier, List<List<String>>> leftKey;
-            Function<GraphElementIdentifier, List<List<String>>> rightKey;
-            if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
-                leftKey = sourceKey;
-                rightKey = destKey;
-
-            } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
-                leftKey = destKey;
-                rightKey = sourceKey;
-            }
-            lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(leftVertex));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildNonFoldedEdgeAction(leftVertex, edgePatternExpr, leftKey));
-            lowerEnvironment.acceptAction(
-                    environmentActionFactory.buildBoundVertexAction(rightVertex, edgePatternExpr, rightKey));
-        }
-        return edgePatternExpr;
-    }
-}
\ No newline at end of file
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java
deleted file mode 100644
index fe4b8b8..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.context.Scope;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByAggregationSugarVisitor;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-
-/**
- * An extension of {@link SqlppGroupByAggregationSugarVisitor} to properly handle {@link CorrLetClause} nodes.
- */
-public class GroupByAggSugarVisitor extends SqlppGroupByAggregationSugarVisitor
-        implements ILetCorrelateClauseVisitor<Expression, ILangExpression> {
-    public GroupByAggSugarVisitor(LangRewritingContext context, Collection<VarIdentifier> externalVars) {
-        super(context, externalVars);
-    }
-
-    @Override
-    public Expression visit(CorrLetClause corrLetClause, ILangExpression arg) throws CompilationException {
-        // Do NOT extend the current scope.
-        corrLetClause.setRightExpression(visit(corrLetClause.getRightExpression(), corrLetClause));
-        VariableExpr varExpr = corrLetClause.getRightVariable();
-        Scope currentScope = scopeChecker.getCurrentScope();
-        if (currentScope.findLocalSymbol(varExpr.getVar().getValue()) != null) {
-            throw new CompilationException(ErrorCode.COMPILATION_ERROR, varExpr.getSourceLocation(),
-                    "Duplicate alias definitions: " + SqlppVariableUtil.toUserDefinedName(varExpr.getVar().getValue()));
-        }
-        currentScope.addNewVarSymbolToScope(varExpr.getVar(), Collections.emptySet());
-        return null;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
deleted file mode 100644
index dd8a89d..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-
-public interface ILetCorrelateClauseVisitor<R, T> extends ILangVisitor<R, T> {
-    R visit(CorrLetClause lcc, T arg) throws CompilationException;
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java
deleted file mode 100644
index 1b8bd4b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.resolve.QueryKnowledgeTable;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.struct.Identifier;
-
-/**
- * Unify the labels across vertices of the same variable name.
- *
- * @see org.apache.asterix.graphix.lang.rewrites.resolve.InferenceBasedResolver
- */
-public class LabelConsistencyVisitor extends AbstractGraphixQueryVisitor {
-    private final QueryKnowledgeTable queryKnowledgeTable;
-
-    public LabelConsistencyVisitor(QueryKnowledgeTable queryKnowledgeTable) {
-        this.queryKnowledgeTable = queryKnowledgeTable;
-    }
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-        if (vertexPatternExpr.getLabels().isEmpty()) {
-            Identifier vertexIdentifier = vertexPatternExpr.getVariableExpr().getVar();
-            if (queryKnowledgeTable.containsKey(vertexIdentifier)) {
-                vertexPatternExpr.getLabels().addAll(queryKnowledgeTable.get(vertexIdentifier));
-            }
-        }
-        return vertexPatternExpr;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java
deleted file mode 100644
index 7da0df5..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Supplier;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Clause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.hyracks.algebricks.common.utils.Pair;
-
-/**
- * A pre-Graphix transformation pass to populate a number of unknowns in our Graphix AST.
- * a) Populate all unknown graph elements (vertices and edges).
- * b) Populate all unknown column names in SELECT-CLAUSEs.
- * c) Populate all unknown GROUP-BY keys.
- * d) Fill in all GROUP-BY fields.
- */
-public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
-    private final GenerateColumnNameVisitor generateColumnNameVisitor;
-    private final Supplier<VarIdentifier> newVariableSupplier;
-
-    public PopulateUnknownsVisitor(GraphixRewritingContext graphixRewritingContext) {
-        generateColumnNameVisitor = new GenerateColumnNameVisitor(graphixRewritingContext);
-        newVariableSupplier = graphixRewritingContext::getNewGraphixVariable;
-    }
-
-    @Override
-    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        selectExpression.accept(generateColumnNameVisitor, arg);
-        return super.visit(selectExpression, arg);
-    }
-
-    @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        super.visit(graphSelectBlock, arg);
-
-        if (graphSelectBlock.hasGroupbyClause()) {
-            // Collect all variables that should belong in the GROUP-BY field list.
-            List<VarIdentifier> userLiveVariables = new ArrayList<>();
-            for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
-                for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
-                    if (pathExpression.getVariableExpr() != null) {
-                        userLiveVariables.add(pathExpression.getVariableExpr().getVar());
-                    }
-                    for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
-                        VarIdentifier vertexVariable = vertexExpression.getVariableExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(vertexVariable)) {
-                            userLiveVariables.add(vertexVariable);
-                        }
-                    }
-                    for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
-                        VarIdentifier edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(edgeVariable)) {
-                            userLiveVariables.add(edgeVariable);
-                        }
-                    }
-                }
-            }
-            if (!graphSelectBlock.getFromGraphClause().getCorrelateClauses().isEmpty()) {
-                FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
-                List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
-                for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
-                    VarIdentifier bindingVariable = correlateClause.getRightVariable().getVar();
-                    if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
-                        userLiveVariables.add(bindingVariable);
-                    }
-                }
-            }
-            if (graphSelectBlock.hasLetWhereClauses()) {
-                for (AbstractClause abstractClause : graphSelectBlock.getLetWhereList()) {
-                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
-                        LetClause letClause = (LetClause) abstractClause;
-                        VarIdentifier bindingVariable = letClause.getVarExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
-                            userLiveVariables.add(bindingVariable);
-                        }
-                    }
-                }
-            }
-
-            // Add the live variables to our GROUP-BY field list.
-            List<Pair<Expression, Identifier>> newGroupFieldList = new ArrayList<>();
-            for (VarIdentifier userLiveVariable : userLiveVariables) {
-                String variableName = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getValue());
-                VariableExpr variableExpr = new VariableExpr(userLiveVariable);
-                newGroupFieldList.add(new Pair<>(variableExpr, new Identifier(variableName)));
-            }
-            graphSelectBlock.getGroupbyClause().setGroupFieldList(newGroupFieldList);
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
-        if (vertexExpression.getVariableExpr() == null) {
-            vertexExpression.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
-        }
-        return super.visit(vertexExpression, arg);
-    }
-
-    @Override
-    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
-        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
-        if (edgeDescriptor.getVariableExpr() == null) {
-            edgeDescriptor.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
-        }
-        return super.visit(edgeExpression, arg);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java
deleted file mode 100644
index eea6b98..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.Collection;
-import java.util.Collections;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.context.Scope;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.rewrites.visitor.VariableCheckAndRewriteVisitor;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-
-/**
- * An extension of {@link VariableCheckAndRewriteVisitor} to properly handle {@link CorrLetClause} nodes.
- */
-public class PostRewriteVariableVisitor extends VariableCheckAndRewriteVisitor
-        implements ILetCorrelateClauseVisitor<Expression, ILangExpression> {
-    public PostRewriteVariableVisitor(LangRewritingContext context, MetadataProvider metadataProvider,
-            Collection<VarIdentifier> externalVars) {
-        super(context, metadataProvider, externalVars);
-    }
-
-    @Override
-    public Expression visit(CorrLetClause corrLetClause, ILangExpression arg) throws CompilationException {
-        // Do NOT extend the current scope.
-        corrLetClause.setRightExpression(visit(corrLetClause.getRightExpression(), corrLetClause));
-        VariableExpr varExpr = corrLetClause.getRightVariable();
-        Scope currentScope = scopeChecker.getCurrentScope();
-        if (currentScope.findLocalSymbol(varExpr.getVar().getValue()) != null) {
-            throw new CompilationException(ErrorCode.COMPILATION_ERROR, varExpr.getSourceLocation(),
-                    "Duplicate alias definitions: " + SqlppVariableUtil.toUserDefinedName(varExpr.getVar().getValue()));
-        }
-        currentScope.addNewVarSymbolToScope(varExpr.getVar(), Collections.emptySet());
-        return null;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java
deleted file mode 100644
index d56419b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.resolve.QueryKnowledgeTable;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-
-/**
- * @see QueryKnowledgeTable
- */
-public class QueryKnowledgeVisitor extends AbstractGraphixQueryVisitor {
-    private final QueryKnowledgeTable queryKnowledgeTable = new QueryKnowledgeTable();
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-        queryKnowledgeTable.put(vertexPatternExpr);
-        return vertexPatternExpr;
-    }
-
-    public QueryKnowledgeTable getQueryKnowledgeTable() {
-        return queryKnowledgeTable;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java
deleted file mode 100644
index f91ebb7..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isEdgeFunction;
-import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isVertexFunction;
-import static org.apache.asterix.graphix.function.GraphixFunctionMap.getFunctionPrepare;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.function.prepare.IFunctionPrepare;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-
-/**
- * Perform a pass to enrich any {@link CorrLetClause} nodes with necessary schema information. Note that this
- * process may overestimate the amount of enrichment actually required (to minimize this difference requires some form
- * of equivalence classes @ the rewriter level).
- */
-public class SchemaEnrichmentVisitor extends AbstractGraphixQueryVisitor {
-    private final ElementLookupTable elementLookupTable;
-    private final Set<FunctionIdentifier> functionIdentifiers;
-    private final Map<VarIdentifier, Expression> expressionMap;
-    private final GraphSelectBlock workingSelectBlock;
-    private final GraphIdentifier graphIdentifier;
-
-    public SchemaEnrichmentVisitor(ElementLookupTable elementLookupTable, GraphIdentifier graphIdentifier,
-            GraphSelectBlock workingSelectBlock, Set<FunctionIdentifier> functionIdentifiers) {
-        this.graphIdentifier = graphIdentifier;
-        this.elementLookupTable = elementLookupTable;
-        this.workingSelectBlock = workingSelectBlock;
-        this.functionIdentifiers = functionIdentifiers;
-        this.expressionMap = new HashMap<>();
-    }
-
-    @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        // Visit our immediate MATCH AST nodes (do not visit lower levels).
-        for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
-            matchClause.accept(this, arg);
-        }
-
-        // We are going to enrich these LET-CORRELATE clauses w/ the necessary schema.
-        if (workingSelectBlock.equals(graphSelectBlock)) {
-            List<CorrLetClause> corrLetClauses = graphSelectBlock.getFromClause().getFromTerms().get(0)
-                    .getCorrelateClauses().stream().filter(c -> c instanceof CorrLetClause).map(c -> (CorrLetClause) c)
-                    .collect(Collectors.toList());
-            Collections.reverse(corrLetClauses);
-            for (FunctionIdentifier functionIdentifier : functionIdentifiers) {
-                IFunctionPrepare functionPrepare = getFunctionPrepare(functionIdentifier);
-                Set<VarIdentifier> visitedBindings = new HashSet<>();
-                for (CorrLetClause corrLetClause : corrLetClauses) {
-                    VarIdentifier rightVar = corrLetClause.getRightVariable().getVar();
-                    Expression graphExpr = expressionMap.getOrDefault(rightVar, null);
-                    boolean isVertex = isVertexFunction(functionIdentifier) && graphExpr instanceof VertexPatternExpr;
-                    boolean isEdge = isEdgeFunction(functionIdentifier) && graphExpr instanceof EdgePatternExpr;
-                    if (!visitedBindings.contains(rightVar) && (isEdge || isVertex)) {
-                        Expression outputExpr = functionPrepare.prepare(corrLetClause.getRightExpression(), graphExpr,
-                                graphIdentifier, elementLookupTable);
-                        corrLetClause.setRightExpression(outputExpr);
-                        visitedBindings.add(rightVar);
-                    }
-                }
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
-        VariableExpr variableExpr = vertexPatternExpr.getVariableExpr();
-        if (variableExpr != null) {
-            expressionMap.put(variableExpr.getVar(), vertexPatternExpr);
-        }
-        return super.visit(vertexPatternExpr, arg);
-    }
-
-    @Override
-    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        VariableExpr variableExpr = edgeDescriptor.getVariableExpr();
-        if (variableExpr != null) {
-            expressionMap.put(variableExpr.getVar(), edgePatternExpr);
-        }
-        return super.visit(edgePatternExpr, arg);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java
deleted file mode 100644
index 36969e2..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.optype.MatchType;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.common.EdgeDependencyGraph;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-
-/**
- * Perform an analysis pass of our AST to generate three items: a) a list of edge orderings, b) a list of dangling
- * vertices, and c) a list of path patterns.
- * - To generate an {@link EdgeDependencyGraph}, we create an adjacency map of query edges whose adjacency between
- * other query edges is defined as sharing some vertex. The adjacency list associated with each query edge is ordered
- * by visitation, and will be used in {@link EdgeDependencyGraph#iterator()}- in short, this means that the order of
- * the patterns in query may affect the resulting lowered AST.
- * - For each LEFT-MATCH-CLAUSE, we build a new dependency subgraph. Every subsequent subgraph should possess a
- * leading edge that contains a vertex found in the previous subgraph (if it exists).
- */
-public class StructureAnalysisVisitor extends AbstractGraphixQueryVisitor {
-    private static class AnalysisEnvironment {
-        // We must populate these maps (edge ID -> edge IDs).
-        private final List<Map<Identifier, List<Identifier>>> adjacencyMaps;
-        private final Map<Identifier, EdgePatternExpr> edgePatternMap;
-        private Map<Identifier, List<Identifier>> workingAdjacencyMap;
-
-        // Build a separate map to define dependencies (vertex ID -> edge IDs).
-        private final Map<Identifier, List<Identifier>> vertexEdgeMap;
-        private final Deque<List<VertexPatternExpr>> danglingVertices;
-        private final Deque<List<PathPatternExpr>> pathPatterns;
-
-        private AnalysisEnvironment() {
-            this.adjacencyMaps = new ArrayList<>();
-            this.edgePatternMap = new LinkedHashMap<>();
-            this.workingAdjacencyMap = new LinkedHashMap<>();
-
-            this.pathPatterns = new ArrayDeque<>();
-            this.danglingVertices = new ArrayDeque<>();
-            this.pathPatterns.addLast(new ArrayList<>());
-            this.danglingVertices.addLast(new ArrayList<>());
-            this.vertexEdgeMap = new LinkedHashMap<>();
-        }
-    }
-
-    // We will return instances of the following back to our caller.
-    public static class StructureContext {
-        private final Deque<List<PathPatternExpr>> pathPatternQueue;
-        private final Deque<List<VertexPatternExpr>> danglingVertexQueue;
-        private final EdgeDependencyGraph edgeDependencyGraph;
-        private final GraphIdentifier graphIdentifier;
-
-        private StructureContext(EdgeDependencyGraph edgeGraph, Deque<List<PathPatternExpr>> pathPatternQueue,
-                Deque<List<VertexPatternExpr>> danglingVertexQueue, GraphIdentifier graphIdentifier) {
-            this.edgeDependencyGraph = edgeGraph;
-            this.pathPatternQueue = pathPatternQueue;
-            this.danglingVertexQueue = danglingVertexQueue;
-            this.graphIdentifier = graphIdentifier;
-        }
-
-        public EdgeDependencyGraph getEdgeDependencyGraph() {
-            return edgeDependencyGraph;
-        }
-
-        public Deque<List<VertexPatternExpr>> getDanglingVertexQueue() {
-            return danglingVertexQueue;
-        }
-
-        public Deque<List<PathPatternExpr>> getPathPatternQueue() {
-            return pathPatternQueue;
-        }
-
-        public GraphIdentifier getGraphIdentifier() {
-            return graphIdentifier;
-        }
-    }
-
-    // We will build new environments on each visit of a FROM-GRAPH-CLAUSE.
-    private final Map<FromGraphClause, AnalysisEnvironment> analysisEnvironmentMap;
-    private final GraphixRewritingContext graphixRewritingContext;
-    private AnalysisEnvironment workingEnvironment;
-
-    public StructureAnalysisVisitor(GraphixRewritingContext graphixRewritingContext) {
-        this.analysisEnvironmentMap = new HashMap<>();
-        this.graphixRewritingContext = graphixRewritingContext;
-    }
-
-    private void addEdgeDependency(VarIdentifier vertexID, VarIdentifier edgeID, List<Identifier> dependencyList) {
-        if (workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
-            dependencyList.addAll(workingEnvironment.vertexEdgeMap.get(vertexID));
-            workingEnvironment.vertexEdgeMap.get(vertexID).add(edgeID);
-
-        } else {
-            List<Identifier> vertexDependencies = new ArrayList<>();
-            vertexDependencies.add(edgeID);
-            workingEnvironment.vertexEdgeMap.put(vertexID, vertexDependencies);
-        }
-    }
-
-    @Override
-    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-        // Add to our a map a new analysis environment.
-        workingEnvironment = new AnalysisEnvironment();
-        analysisEnvironmentMap.put(fromGraphClause, workingEnvironment);
-
-        // Collect our structure context.
-        return super.visit(fromGraphClause, arg);
-    }
-
-    @Override
-    public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
-        // Reset to build a new graph.
-        if (matchClause.getMatchType() == MatchType.LEFTOUTER) {
-            workingEnvironment.danglingVertices.addLast(new ArrayList<>());
-            workingEnvironment.pathPatterns.addLast(new ArrayList<>());
-            workingEnvironment.vertexEdgeMap.clear();
-
-            // We should retain all adjacency information from the previous walks.
-            workingEnvironment.adjacencyMaps.add(workingEnvironment.workingAdjacencyMap);
-            workingEnvironment.workingAdjacencyMap = new LinkedHashMap<>(workingEnvironment.workingAdjacencyMap);
-        }
-        return super.visit(matchClause, arg);
-    }
-
-    @Override
-    public Expression visit(PathPatternExpr pathExpression, ILangExpression arg) throws CompilationException {
-        // Visit our edges first, then our vertices. This allows us to determine our dangling vertices.
-        for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
-            edgeExpression.accept(this, arg);
-        }
-        for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
-            vertexExpression.accept(this, arg);
-        }
-        workingEnvironment.pathPatterns.getLast().add(pathExpression);
-        return pathExpression;
-    }
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
-        VarIdentifier vertexID = vertexExpression.getVariableExpr().getVar();
-        if (!workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
-            workingEnvironment.danglingVertices.getLast().add(vertexExpression);
-        }
-        return vertexExpression;
-    }
-
-    @Override
-    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
-        VarIdentifier leftVertexID = edgeExpression.getLeftVertex().getVariableExpr().getVar();
-        VarIdentifier rightVertexID = edgeExpression.getRightVertex().getVariableExpr().getVar();
-        VarIdentifier edgeID = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
-        List<Identifier> edgeDependencies = new ArrayList<>();
-
-        addEdgeDependency(leftVertexID, edgeID, edgeDependencies);
-        addEdgeDependency(rightVertexID, edgeID, edgeDependencies);
-        workingEnvironment.workingAdjacencyMap.put(edgeID, edgeDependencies);
-        workingEnvironment.edgePatternMap.put(edgeID, edgeExpression);
-        for (Identifier dependencyEdgeID : edgeDependencies) {
-            // Populate our map in reverse as well.
-            workingEnvironment.workingAdjacencyMap.get(dependencyEdgeID).add(edgeID);
-        }
-
-        // We ignore any internal vertices here.
-        return edgeExpression;
-    }
-
-    public Map<FromGraphClause, StructureContext> getFromGraphClauseContextMap() {
-        return analysisEnvironmentMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> {
-            AnalysisEnvironment analysisEnvironment = e.getValue();
-            if (!analysisEnvironment.workingAdjacencyMap.isEmpty()) {
-                // We have not finished building our edge dependency subgraph.
-                analysisEnvironment.adjacencyMaps.add(analysisEnvironment.workingAdjacencyMap);
-                analysisEnvironment.workingAdjacencyMap = new LinkedHashMap<>();
-                analysisEnvironment.vertexEdgeMap.clear();
-            }
-
-            // Build our graph identifier.
-            FromGraphClause fromGraphClause = e.getKey();
-            MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
-            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
-                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
-            String graphName = (fromGraphClause.getGraphName() != null) ? fromGraphClause.getGraphName().getValue()
-                    : fromGraphClause.getGraphConstructor().getInstanceID();
-            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName);
-
-            // Construct our output.
-            List<Map<Identifier, List<Identifier>>> adjacencyMaps = e.getValue().adjacencyMaps;
-            Map<Identifier, EdgePatternExpr> edgePatternMap = e.getValue().edgePatternMap;
-            EdgeDependencyGraph edgeDependencyGraph = new EdgeDependencyGraph(adjacencyMaps, edgePatternMap);
-            Deque<List<VertexPatternExpr>> danglingVertices = e.getValue().danglingVertices;
-            Deque<List<PathPatternExpr>> pathPatterns = e.getValue().pathPatterns;
-            return new StructureContext(edgeDependencyGraph, pathPatterns, danglingVertices, graphIdentifier);
-        }));
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java
deleted file mode 100644
index 38bebe9..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
-
-import java.util.Map;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.resolve.IGraphElementResolver;
-import org.apache.asterix.graphix.lang.rewrites.resolve.InferenceBasedResolver;
-import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
-import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.metadata.entity.schema.Graph;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.metadata.MetadataTransactionContext;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-
-/**
- * Resolve graph element labels and edge directions in our AST. We assume that all graph elements have a variable.
- *
- * @see InferenceBasedResolver
- */
-public class StructureResolutionVisitor extends AbstractGraphixQueryVisitor {
-    private static final Logger LOGGER = LogManager.getLogger(StructureResolutionVisitor.class);
-
-    // If we exceed 500 iterations, something is probably wrong... log this.
-    private static final long DEFAULT_RESOLVER_ITERATION_MAX = 500;
-
-    private final MetadataProvider metadataProvider;
-    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
-
-    public StructureResolutionVisitor(GraphixRewritingContext graphixRewritingContext) {
-        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
-        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
-    }
-
-    @Override
-    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-        // Establish our schema knowledge.
-        SchemaKnowledgeTable schemaKnowledgeTable;
-        if (fromGraphClause.getGraphConstructor() == null) {
-            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
-                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
-            Identifier graphName = fromGraphClause.getGraphName();
-
-            // First, try to find our graph inside our declared graph set.
-            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
-            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
-            if (declaredGraph != null) {
-                schemaKnowledgeTable = new SchemaKnowledgeTable(declaredGraph.getGraphConstructor());
-
-            } else {
-                // Otherwise, fetch the graph from our metadata.
-                try {
-                    MetadataTransactionContext metadataTxnContext = metadataProvider.getMetadataTxnContext();
-                    Graph graphFromMetadata = getGraph(metadataTxnContext, dataverseName, graphName.getValue());
-                    if (graphFromMetadata == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                                "Graph " + graphName.getValue() + " does not exist.");
-                    }
-                    schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
-
-                } catch (AlgebricksException e) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                            "Graph " + graphName.getValue() + " does not exist.");
-                }
-            }
-
-        } else {
-            schemaKnowledgeTable = new SchemaKnowledgeTable(fromGraphClause.getGraphConstructor());
-        }
-
-        // Determine our resolution strategy. By default, we will perform bare-bones resolution.
-        IGraphElementResolver graphElementResolver;
-        String resolverMetadataKeyName = GraphixCompilationProvider.RESOLVER_METADATA_CONFIG;
-        if (metadataProvider.getConfig().containsKey(resolverMetadataKeyName)) {
-            String resolverProperty = metadataProvider.getProperty(resolverMetadataKeyName);
-            if (resolverProperty.equalsIgnoreCase(InferenceBasedResolver.METADATA_CONFIG_NAME)) {
-                graphElementResolver = new InferenceBasedResolver(schemaKnowledgeTable);
-
-            } else {
-                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, resolverProperty);
-            }
-
-        } else {
-            graphElementResolver = new InferenceBasedResolver(schemaKnowledgeTable);
-        }
-
-        // Perform our resolution passes (repeat until we reach a fixed point or the iteration max).
-        String resolverIterationMaxMetadataKeyName = GraphixCompilationProvider.RESOLVER_ITERATION_MAX_METADATA_CONFIG;
-        long resolverIterationMax;
-        if (metadataProvider.getConfig().containsKey(resolverIterationMaxMetadataKeyName)) {
-            String resolverIterationMaxProperty = metadataProvider.getProperty(resolverIterationMaxMetadataKeyName);
-            try {
-                resolverIterationMax = Long.parseLong(resolverIterationMaxProperty);
-
-            } catch (NumberFormatException e) {
-                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, resolverIterationMaxProperty);
-            }
-
-        } else {
-            resolverIterationMax = DEFAULT_RESOLVER_ITERATION_MAX;
-        }
-        for (int i = 0; i < resolverIterationMax && !graphElementResolver.isAtFixedPoint(); i++) {
-            graphElementResolver.resolve(fromGraphClause);
-            if (i == resolverIterationMax - 1) {
-                LOGGER.warn("Number of iterations for element resolution has exceeded " + resolverIterationMax);
-            }
-        }
-
-        // Perform the final pass of our FROM-GRAPH-CLAUSE.
-        new AbstractGraphixQueryVisitor() {
-            @Override
-            public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-                if (vertexPatternExpr.getLabels().isEmpty()) {
-                    vertexPatternExpr.getLabels().addAll(schemaKnowledgeTable.getVertexLabelSet());
-                }
-                return vertexPatternExpr;
-            }
-
-            @Override
-            public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
-                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-                if (edgeDescriptor.getEdgeLabels().isEmpty()) {
-                    edgeDescriptor.getEdgeLabels().addAll(schemaKnowledgeTable.getEdgeLabelSet());
-                }
-                for (VertexPatternExpr internalVertex : edgePatternExpr.getInternalVertices()) {
-                    internalVertex.accept(this, arg);
-                }
-                return edgePatternExpr;
-            }
-        }.visit(fromGraphClause, null);
-
-        return null;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java
deleted file mode 100644
index 2f99a94..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
-import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
-
-/**
- * Substitute qualifying {@link VariableExpr} nodes (via their {@link VarIdentifier}) with a deep-copy of an expression.
- */
-public class VariableSubstitutionVisitor extends AbstractSqlppExpressionScopingVisitor {
-    private final Map<VarIdentifier, Expression> substitutionMap = new HashMap<>();
-
-    public VariableSubstitutionVisitor(LangRewritingContext context) {
-        super(context);
-    }
-
-    public void addSubstitution(VarIdentifier varIdentifier, Expression substitution) {
-        substitutionMap.put(varIdentifier, substitution);
-    }
-
-    @Override
-    public Expression visit(VariableExpr variableExpr, ILangExpression arg) throws CompilationException {
-        Expression substitution = substitutionMap.getOrDefault(variableExpr.getVar(), variableExpr);
-        if (substitution != null) {
-            substitution = (Expression) SqlppRewriteUtil.deepCopy(substitution);
-        }
-        return substitution;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
index 730db34..235a45f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
@@ -26,8 +26,8 @@
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
 import org.apache.asterix.graphix.lang.util.GraphStatementHandlingUtil;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.translator.IRequestParameters;
@@ -37,10 +37,12 @@
 
 /**
  * Statement for storing a {@link GraphConstructor} instance in our metadata.
- * - A CREATE GRAPH statement MUST always include a graph name.
- * - We can specify "CREATE OR REPLACE" to perform an upsert of our graph.
- * - We can specify "CREATE ... IF NOT EXISTS" to insert the graph if it doesn't exist, and not raise an error if the
- * graph already exists.
+ * <ul>
+ *  <li>A CREATE GRAPH statement MUST always include a graph name.</li>
+ *  <li>We can specify "CREATE OR REPLACE" to perform an upsert of our graph.</li>
+ *  <li>We can specify "CREATE ... IF NOT EXISTS" to insert the graph if it doesn't exist, and not raise an error if the
+ *  graph already exists.</li>
+ * </ul>
  */
 public class CreateGraphStatement extends ExtensionStatement {
     private final GraphConstructor graphConstructor;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java
index b5fd970..b1b0e14 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java
@@ -25,7 +25,7 @@
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.graphix.app.translator.GraphixQueryTranslator;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.translator.IRequestParameters;
@@ -33,7 +33,7 @@
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 
 /**
- * Statement for storing a {@link GraphConstructor} instance in our _context_ instead of our metadata.
+ * Statement for storing a {@link GraphConstructor} instance in our <i>context</i> instead of our metadata.
  */
 public class DeclareGraphStatement extends ExtensionStatement {
     private final GraphConstructor graphConstructor;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
index 9aadb97..e21286f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
@@ -24,8 +24,8 @@
 import org.apache.asterix.app.translator.QueryTranslator;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
 import org.apache.asterix.graphix.lang.util.GraphStatementHandlingUtil;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.translator.IRequestParameters;
@@ -36,9 +36,11 @@
 /**
  * Statement for removing a {@link org.apache.asterix.graphix.lang.expression.GraphConstructor} instance from our
  * metadata.
- * - A DROP GRAPH statement MUST always include a graph name.
- * - We can specify "DROP ... IF EXISTS" to drop the graph if it exists, and not raise an error if the graph doesn't
- * exist.
+ * <ul>
+ *  <li>A DROP GRAPH statement MUST always include a graph name.</li>
+ *  <li>We can specify "DROP ... IF EXISTS" to drop the graph if it exists, and not raise an error if the graph
+ *  doesn't exist,</li>
+ * </ul>
  */
 public class GraphDropStatement extends ExtensionStatement {
     private final DataverseName dataverseName;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
index 790a6be..32b720c 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
@@ -23,9 +23,9 @@
 import org.apache.asterix.algebra.extension.ExtensionStatement;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
+import org.apache.asterix.graphix.lang.visitor.base.IGraphixLangVisitor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
@@ -38,16 +38,16 @@
  * use this class to store the directly parsed AST and a normalized AST for the bodies themselves.
  */
 public final class GraphElementDeclaration extends ExtensionStatement {
-    private final GraphElementIdentifier identifier;
+    private final IElementIdentifier identifier;
     private final Expression rawBody;
     private Expression normalizedBody;
 
-    public GraphElementDeclaration(GraphElementIdentifier identifier, Expression rawBody) {
+    public GraphElementDeclaration(IElementIdentifier identifier, Expression rawBody) {
         this.identifier = Objects.requireNonNull(identifier);
         this.rawBody = Objects.requireNonNull(rawBody);
     }
 
-    public GraphElementIdentifier getIdentifier() {
+    public IElementIdentifier getIdentifier() {
         return identifier;
     }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
index d5f88e4..094cdb4 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
@@ -19,23 +19,24 @@
 package org.apache.asterix.graphix.lang.struct;
 
 import java.io.Serializable;
-import java.util.List;
 import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 
 /**
  * Descriptor for a query edge instance. A query edge has the following:
- * 1. A set of edge labels.
- * 2. A variable associated with the query edge.
- * 3. A pattern type. An edge pattern can either be a pure edge, or a sub-path.
- * 4. A minimum number of hops (allowed to be NULL, indicating a minimum of 1 hop).
- * 5. A maximum number of hops (not allowed to be NULL).
- * 6. An edge direction (left to right, right to left, or undirected).
+ * <ul>
+ *  <li>A set of edge labels.</li>
+ *  <li>A variable associated with the query edge.</li>
+ *  <li>A pattern type. An edge pattern can either be a pure edge, or a sub-path.</li>
+ *  <li>A minimum number of hops (allowed to be NULL, indicating a minimum of 1 hop).</li>
+ *  <li>A maximum number of hops (allowed to be NULL, indicating an unbounded maximum).</li>
+ *  <li>An edge direction (left to right, right to left, or undirected).</li>
+ *  <li>A filter expression (allowed to be NULL).</li>
+ * </ul>
  */
 public class EdgeDescriptor implements Serializable {
     private static final long serialVersionUID = 1L;
@@ -44,18 +45,20 @@
     private final Integer minimumHops;
     private final Integer maximumHops;
     private final PatternType patternType;
+    private final Expression filterExpr;
 
     // We must be able to assign variables to our edges, as well as change the direction of UNDIRECTED edges.
     private VariableExpr variableExpr;
     private EdgeDirection edgeDirection;
 
     public EdgeDescriptor(EdgeDirection edgeDirection, PatternType patternType, Set<ElementLabel> edgeLabels,
-            VariableExpr variableExpr, Integer minimumHops, Integer maximumHops) {
-        this.edgeDirection = edgeDirection;
+            Expression filterExpr, VariableExpr variableExpr, Integer minimumHops, Integer maximumHops) {
         this.edgeLabels = edgeLabels;
+        this.patternType = patternType;
         this.minimumHops = minimumHops;
         this.maximumHops = maximumHops;
-        this.patternType = patternType;
+        this.filterExpr = filterExpr;
+        this.edgeDirection = edgeDirection;
         this.variableExpr = variableExpr;
     }
 
@@ -83,6 +86,10 @@
         return patternType;
     }
 
+    public Expression getFilterExpr() {
+        return filterExpr;
+    }
+
     public VariableExpr getVariableExpr() {
         return variableExpr;
     }
@@ -91,15 +98,9 @@
         this.variableExpr = variableExpr;
     }
 
-    public List<GraphElementIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
-        return edgeLabels.stream()
-                .map(e -> new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.EDGE, e))
-                .collect(Collectors.toList());
-    }
-
     @Override
     public int hashCode() {
-        return Objects.hash(edgeDirection, patternType, edgeLabels, variableExpr, minimumHops, maximumHops);
+        return Objects.hash(edgeDirection, patternType, edgeLabels, variableExpr, filterExpr, minimumHops, maximumHops);
     }
 
     @Override
@@ -115,6 +116,7 @@
                 && Objects.equals(this.patternType, that.patternType)
                 && Objects.equals(this.edgeLabels, that.edgeLabels)
                 && Objects.equals(this.variableExpr, that.variableExpr)
+                && Objects.equals(this.filterExpr, that.filterExpr)
                 && Objects.equals(this.minimumHops, that.minimumHops)
                 && Objects.equals(this.maximumHops, that.maximumHops);
     }
@@ -123,16 +125,31 @@
     public String toString() {
         String labelsString = edgeLabels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
         String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
-        String subPathString = (patternType != PatternType.PATH) ? ""
-                : "{" + ((minimumHops == null) ? "" : minimumHops) + "," + maximumHops + "}";
-        return String.format("%s-[%s:(%s)%s]-%s", (edgeDirection == EdgeDirection.LEFT_TO_RIGHT) ? "" : "<",
-                variableString, labelsString, subPathString, (edgeDirection == EdgeDirection.RIGHT_TO_LEFT) ? "" : ">");
+        String minHopsString = ((minimumHops == null) ? "" : minimumHops.toString());
+        String maxHopsString = ((maximumHops == null) ? "" : maximumHops.toString());
+        String subPathString = (patternType != PatternType.PATH) ? "" : "{" + minHopsString + "," + maxHopsString + "}";
+        String filterString = (filterExpr == null) ? "" : (" WHERE " + filterExpr + " ");
+        return String.format("%s-[%s:(%s)%s%s]-%s", (edgeDirection == EdgeDirection.LEFT_TO_RIGHT) ? "" : "<",
+                variableString, labelsString, subPathString, filterString,
+                (edgeDirection == EdgeDirection.RIGHT_TO_LEFT) ? "" : ">");
     }
 
     public enum EdgeDirection {
-        LEFT_TO_RIGHT,
-        RIGHT_TO_LEFT,
-        UNDIRECTED
+        LEFT_TO_RIGHT("L2R"),
+        RIGHT_TO_LEFT("R2L"),
+        UNDIRECTED("U");
+
+        // For printing purposes...
+        private final String shortName;
+
+        EdgeDirection(String shortName) {
+            this.shortName = shortName;
+        }
+
+        @Override
+        public String toString() {
+            return shortName;
+        }
     }
 
     public enum PatternType {
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java
index a65afc6..a3807ce 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java
@@ -21,41 +21,36 @@
 import java.io.Serializable;
 import java.util.Objects;
 
+/**
+ * Label for a vertex, uniquely identified by the label name.
+ */
 public class ElementLabel implements Serializable {
-    private static final long serialVersionUID = 1L;
+    private static final long serialVersionUID = 2L;
 
     private final String labelName;
-    private boolean isInferred;
+    private final boolean isNegated;
 
-    public ElementLabel(String labelName) {
-        this(labelName, false);
-    }
-
-    private ElementLabel(String labelName, boolean isInferred) {
+    public ElementLabel(String labelName, boolean isNegated) {
         this.labelName = Objects.requireNonNull(labelName);
-        this.isInferred = isInferred;
+        this.isNegated = isNegated;
     }
 
-    public ElementLabel asInferred() {
-        return new ElementLabel(labelName, true);
+    public boolean isNegated() {
+        return isNegated;
     }
 
-    public void markInferred(boolean isInferred) {
-        this.isInferred = isInferred;
-    }
-
-    public boolean isInferred() {
-        return isInferred;
-    }
-
-    @Override
-    public String toString() {
+    public String getLabelName() {
         return labelName;
     }
 
     @Override
+    public String toString() {
+        return (isNegated ? "NOT " : "") + labelName;
+    }
+
+    @Override
     public int hashCode() {
-        return Objects.hashCode(labelName);
+        return Objects.hash(labelName, isNegated);
     }
 
     @Override
@@ -65,7 +60,7 @@
         }
         if (o instanceof ElementLabel) {
             ElementLabel that = (ElementLabel) o;
-            return this.labelName.equals(that.labelName);
+            return this.labelName.equals(that.labelName) && this.isNegated == that.isNegated;
         }
         return false;
     }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/PatternGroup.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/PatternGroup.java
new file mode 100644
index 0000000..7356668
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/PatternGroup.java
@@ -0,0 +1,86 @@
+/*
+ * 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.asterix.graphix.lang.struct;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+
+/**
+ * A container for a collection of vertices and edges. In contrast to the expression
+ * {@link org.apache.asterix.graphix.lang.expression.PathPatternExpr}, the following elements do not have to be
+ * connected.
+ */
+public class PatternGroup implements Iterable<AbstractExpression> {
+    // Users should add to the following sets directly.
+    private final Set<VertexPatternExpr> vertexPatternSet = new HashSet<>();
+    private final Set<EdgePatternExpr> edgePatternSet = new HashSet<>();
+
+    public Set<VertexPatternExpr> getVertexPatternSet() {
+        return vertexPatternSet;
+    }
+
+    public Set<EdgePatternExpr> getEdgePatternSet() {
+        return edgePatternSet;
+    }
+
+    public void replace(VertexPatternExpr searchExpression, VertexPatternExpr replaceExpression) {
+        if (!vertexPatternSet.contains(searchExpression)) {
+            throw new IllegalArgumentException("Vertex pattern not found in group!");
+        }
+        vertexPatternSet.remove(searchExpression);
+        vertexPatternSet.add(replaceExpression);
+    }
+
+    public void replace(EdgePatternExpr searchExpression, EdgePatternExpr replaceExpression) {
+        if (!edgePatternSet.contains(searchExpression)) {
+            throw new IllegalArgumentException("Edge pattern not found in group!");
+        }
+        edgePatternSet.remove(searchExpression);
+        edgePatternSet.add(replaceExpression);
+    }
+
+    @Override
+    public Iterator<AbstractExpression> iterator() {
+        Iterator<VertexPatternExpr> vertexPatternIterator = vertexPatternSet.iterator();
+        Iterator<EdgePatternExpr> edgePatternIterator = edgePatternSet.iterator();
+        return new Iterator<>() {
+            @Override
+            public boolean hasNext() {
+                return vertexPatternIterator.hasNext() || edgePatternIterator.hasNext();
+            }
+
+            @Override
+            public AbstractExpression next() {
+                if (vertexPatternIterator.hasNext()) {
+                    return vertexPatternIterator.next();
+                }
+                if (edgePatternIterator.hasNext()) {
+                    return edgePatternIterator.next();
+                }
+                throw new NoSuchElementException();
+            }
+        };
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
index c4cc975..df2569f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
@@ -30,16 +30,17 @@
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.graphix.app.translator.GraphixQueryTranslator;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
-import org.apache.asterix.graphix.lang.rewrites.GraphixQueryRewriter;
-import org.apache.asterix.graphix.lang.rewrites.visitor.AbstractGraphixQueryVisitor;
+import org.apache.asterix.graphix.lang.rewrite.GraphixQueryRewriter;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.visitor.base.AbstractGraphixQueryVisitor;
 import org.apache.asterix.graphix.metadata.entity.dependency.DependencyIdentifier;
 import org.apache.asterix.graphix.metadata.entity.dependency.GraphRequirements;
 import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
@@ -139,7 +140,7 @@
                     schemaBuilder.addVertex(vertex.getLabel(), vertex.getPrimaryKeyFields(), vertex.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
-                    GraphElementIdentifier id = schemaVertex.getIdentifier();
+                    VertexIdentifier id = schemaVertex.getIdentifier();
                     GraphElementDeclaration decl = new GraphElementDeclaration(id, vertex.getExpression());
                     decl.setSourceLocation(vertex.getSourceLocation());
                     graphElementDeclarations.add(decl);
@@ -160,7 +161,7 @@
                             edge.getDestinationKeyFields(), edge.getSourceKeyFields(), edge.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
-                    GraphElementIdentifier id = schemaEdge.getIdentifier();
+                    EdgeIdentifier id = schemaEdge.getIdentifier();
                     GraphElementDeclaration decl = new GraphElementDeclaration(id, edge.getExpression());
                     decl.setSourceLocation(edge.getSourceLocation());
                     graphElementDeclarations.add(decl);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/AbstractGraphixQueryVisitor.java
similarity index 77%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/AbstractGraphixQueryVisitor.java
index 8fdc0da..c281058 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/AbstractGraphixQueryVisitor.java
@@ -16,11 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.visitor.base;
+
+import java.util.ListIterator;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
@@ -31,11 +32,10 @@
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
 
 public abstract class AbstractGraphixQueryVisitor extends AbstractSqlppSimpleExpressionVisitor
@@ -63,34 +63,13 @@
     }
 
     @Override
-    public Expression visit(SelectBlock sb, ILangExpression arg) throws CompilationException {
-        return (sb instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) sb, arg) : super.visit(sb, arg);
-    }
+    public Expression visit(FromClause fc, ILangExpression arg) throws CompilationException {
+        if (fc instanceof FromGraphClause) {
+            return visit((FromGraphClause) fc, arg);
 
-    @Override
-    public Expression visit(GraphSelectBlock gsb, ILangExpression arg) throws CompilationException {
-        // Traverse in the same order as a regular SELECT-BLOCK: FROM, LET/WHERE, GROUP-BY, LET/HAVING, SELECT.
-        if (gsb.hasFromGraphClause()) {
-            gsb.getFromGraphClause().accept(this, arg);
-
-        } else if (gsb.hasFromClause()) {
-            gsb.getFromClause().accept(this, arg);
+        } else {
+            return super.visit(fc, arg);
         }
-        if (gsb.hasLetWhereClauses()) {
-            for (AbstractClause clause : gsb.getLetWhereList()) {
-                clause.accept(this, arg);
-            }
-        }
-        if (gsb.hasGroupbyClause()) {
-            gsb.getGroupbyClause().accept(this, arg);
-        }
-        if (gsb.hasLetHavingClausesAfterGroupby()) {
-            for (AbstractClause clause : gsb.getLetHavingListAfterGroupby()) {
-                clause.accept(this, arg);
-            }
-        }
-        gsb.getSelectClause().accept(this, arg);
-        return null;
     }
 
     @Override
@@ -110,8 +89,9 @@
 
     @Override
     public Expression visit(MatchClause mc, ILangExpression arg) throws CompilationException {
-        for (PathPatternExpr pathPatternExpr : mc.getPathExpressions()) {
-            pathPatternExpr.accept(this, arg);
+        ListIterator<PathPatternExpr> ppeIterator = mc.getPathExpressions().listIterator();
+        while (ppeIterator.hasNext()) {
+            ppeIterator.set((PathPatternExpr) ppeIterator.next().accept(this, arg));
         }
         return null;
     }
@@ -138,8 +118,11 @@
         if (edgeDescriptor.getVariableExpr() != null) {
             edgeDescriptor.getVariableExpr().accept(this, arg);
         }
-        for (VertexPatternExpr internalVertex : epe.getInternalVertices()) {
-            internalVertex.accept(this, arg);
+        if (epe.getInternalVertex() != null) {
+            epe.getInternalVertex().accept(this, arg);
+        }
+        if (edgeDescriptor.getFilterExpr() != null) {
+            edgeDescriptor.getFilterExpr().accept(this, arg);
         }
         return epe;
     }
@@ -149,6 +132,9 @@
         if (vpe.getVariableExpr() != null) {
             vpe.getVariableExpr().accept(this, arg);
         }
+        if (vpe.getFilterExpr() != null) {
+            vpe.getFilterExpr().accept(this, arg);
+        }
         return vpe;
     }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/IGraphixLangVisitor.java
similarity index 92%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/IGraphixLangVisitor.java
index 9d5ece9..9da17d9 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/visitor/base/IGraphixLangVisitor.java
@@ -16,11 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.visitor.base;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
@@ -47,8 +46,6 @@
 
     R visit(GraphDropStatement gds, T arg) throws CompilationException;
 
-    R visit(GraphSelectBlock gsb, T arg) throws CompilationException;
-
     R visit(FromGraphClause fgc, T arg) throws CompilationException;
 
     R visit(MatchClause mc, T arg) throws CompilationException;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java
index 0b88f27..ec3964e 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java
@@ -29,7 +29,7 @@
 
 /**
  * A collection of {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} dependencies associated with a
- * {@link org.apache.asterix.metadata.entities.Function} instance. This does **not** include non-graph dependencies
+ * {@link org.apache.asterix.metadata.entities.Function} instance. This does <b>not</b> include non-graph dependencies
  * for functions.
  */
 public class FunctionRequirements implements IEntityRequirements {
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java
index 6c84eab..ade330e 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java
@@ -29,7 +29,7 @@
 
 /**
  * A collection of {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} dependencies associated with a view
- * instance. This does **not** include non-graph dependencies for views.
+ * instance. This does <b>not</b> include non-graph dependencies for views.
  */
 public class ViewRequirements implements IEntityRequirements {
     private static final long serialVersionUID = 1L;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
index 1c4cc17..d9a8ca9 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
@@ -21,24 +21,24 @@
 import java.util.List;
 import java.util.Objects;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
 
 /**
  * Metadata representation of an edge. An edge consists of the following:
- * 1. A {@link GraphElementIdentifier}, to uniquely identify the edge across other graph elements.
- * 2. An {@link ElementLabel} instance associated with the source vertex.
- * 3. An {@link ElementLabel} instance associated with the destination vertex.
- * 4. A list of source key fields, associated with the definition body.
- * 5. A list of destination key fields, associated with the definition body.
- * 6. A SQL++ string denoting the definition body.
+ * <ul>
+ *  <li>A {@link EdgeIdentifier}, to uniquely identify the edge across other graph elements.</li>
+ *  <li>An {@link ElementLabel} instance associated with the source vertex.</li>
+ *  <li>An {@link ElementLabel} instance associated with the destination vertex.</li>
+ *  <li>A list of source key fields, associated with the definition body.</li>
+ *  <li>A list of destination key fields, associated with the definition body.</li>
+ *  <li>A SQL++ string denoting the definition body.</li>
+ * </ul>
  */
 public class Edge implements IElement {
     private static final long serialVersionUID = 1L;
 
-    private final GraphElementIdentifier identifier;
-    private final ElementLabel sourceVertexLabel;
-    private final ElementLabel destinationVertexLabel;
+    private final EdgeIdentifier identifier;
     private final List<List<String>> sourceKeyFieldNames;
     private final List<List<String>> destinationKeyFieldNames;
     private final String definitionBody;
@@ -46,11 +46,9 @@
     /**
      * Use {@link Schema.Builder} to build Edge instances instead of this constructor.
      */
-    Edge(GraphElementIdentifier identifier, ElementLabel sourceVertexLabel, ElementLabel destinationVertexLabel,
-            List<List<String>> sourceKeyFieldNames, List<List<String>> destKeyFieldNames, String definitionBody) {
+    Edge(EdgeIdentifier identifier, List<List<String>> sourceKeyFieldNames, List<List<String>> destKeyFieldNames,
+            String definitionBody) {
         this.identifier = Objects.requireNonNull(identifier);
-        this.sourceVertexLabel = Objects.requireNonNull(sourceVertexLabel);
-        this.destinationVertexLabel = Objects.requireNonNull(destinationVertexLabel);
         this.sourceKeyFieldNames = Objects.requireNonNull(sourceKeyFieldNames);
         this.destinationKeyFieldNames = Objects.requireNonNull(destKeyFieldNames);
         this.definitionBody = Objects.requireNonNull(definitionBody);
@@ -82,11 +80,11 @@
     }
 
     public ElementLabel getDestinationLabel() {
-        return destinationVertexLabel;
+        return identifier.getDestinationLabel();
     }
 
     public ElementLabel getSourceLabel() {
-        return sourceVertexLabel;
+        return identifier.getSourceLabel();
     }
 
     public List<List<String>> getSourceKeyFieldNames() {
@@ -98,13 +96,13 @@
     }
 
     @Override
-    public GraphElementIdentifier getIdentifier() {
+    public EdgeIdentifier getIdentifier() {
         return identifier;
     }
 
     @Override
     public ElementLabel getLabel() {
-        return identifier.getElementLabel();
+        return identifier.getEdgeLabel();
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
index 3637a45..0e38e92 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
@@ -20,17 +20,17 @@
 
 import java.io.Serializable;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
 
 /**
  * Metadata interface for a graph element (i.e. edge or vertex). An element has the following:
- * 1. A {@link GraphElementIdentifier}, to uniquely identify the element across other graph elements.
- * 2. A {@link ElementLabel} unique amongst the element classes (e.g. an edge label is unique amongst all graph edges).
+ * 1. A {@link Serializable}, to uniquely identify the element across other graph elements.
+ * 2. A {@link ElementLabel} unique amongst the element classes.
  * 3. A non-null SQL++ string, representing a graph element body.
  */
 public interface IElement extends Serializable {
-    GraphElementIdentifier getIdentifier();
+    IElementIdentifier getIdentifier();
 
     ElementLabel getLabel();
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
index d19590f..c2f9e54 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
@@ -18,23 +18,26 @@
  */
 package org.apache.asterix.graphix.metadata.entity.schema;
 
-import static org.apache.asterix.graphix.common.metadata.GraphElementIdentifier.Kind.EDGE;
-import static org.apache.asterix.graphix.common.metadata.GraphElementIdentifier.Kind.VERTEX;
-
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.EdgeIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
 
 /**
  * Metadata representation of a graph schema. A graph schema consists of:
- * 1. A list of {@link Vertex} instances.
- * 2. A list of {@link Edge} instances, which link the aforementioned vertices.
+ * <ul>
+ *  <li>A list of {@link Vertex} instances.</li>
+ *  <li>A list of {@link Edge} instances, which link the aforementioned vertices.</li>
+ * </ul>
  */
 public class Schema implements Serializable {
     private static final long serialVersionUID = 1L;
@@ -59,7 +62,7 @@
 
     public static class Builder {
         private final Map<ElementLabel, Vertex> vertexLabelMap = new HashMap<>();
-        private final Map<ElementLabel, List<Edge>> edgeLabelMap = new HashMap<>();
+        private final Map<EdgeLabel, List<Edge>> edgeLabelMap = new HashMap<>();
 
         // We aim to populate the schema object below.
         private final Schema workingSchema;
@@ -74,12 +77,12 @@
         /**
          * @return Null if a vertex with the same label already exists. The vertex to-be-added otherwise.
          */
-        public Vertex addVertex(ElementLabel vertexLabel, List<List<String>> primaryKeyFieldNames, String definition) {
-            if (!vertexLabelMap.containsKey(vertexLabel)) {
-                GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier, VERTEX, vertexLabel);
+        public Vertex addVertex(ElementLabel elementLabel, List<List<String>> primaryKeyFieldNames, String definition) {
+            if (!vertexLabelMap.containsKey(elementLabel)) {
+                VertexIdentifier identifier = new VertexIdentifier(graphIdentifier, elementLabel);
                 Vertex newVertex = new Vertex(identifier, primaryKeyFieldNames, definition);
                 workingSchema.vertexList.add(newVertex);
-                vertexLabelMap.put(vertexLabel, newVertex);
+                vertexLabelMap.put(elementLabel, newVertex);
                 return newVertex;
 
             } else {
@@ -102,22 +105,27 @@
             } else if (!vertexLabelMap.containsKey(destinationLabel)) {
                 lastError = Error.DESTINATION_VERTEX_NOT_FOUND;
                 return null;
+            }
 
-            } else if (edgeLabelMap.containsKey(edgeLabel)) {
+            // Ensure we have unique <source, edge, dest> triples.
+            EdgeLabel edgePatternLabel = new EdgeLabel();
+            edgePatternLabel.endpointLabels.add(sourceLabel);
+            edgePatternLabel.endpointLabels.add(destinationLabel);
+            edgePatternLabel.edgeLabel = edgeLabel;
+            if (edgeLabelMap.containsKey(edgePatternLabel)) {
                 lastError = Error.EDGE_LABEL_CONFLICT;
                 return null;
             }
 
             // Update our schema.
-            GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier, EDGE, edgeLabel);
-            Edge newEdge = new Edge(identifier, sourceLabel, destinationLabel, sourceKeyFieldNames,
-                    destinationKeyFieldNames, definitionBody);
+            EdgeIdentifier identifier = new EdgeIdentifier(graphIdentifier, sourceLabel, edgeLabel, destinationLabel);
+            Edge newEdge = new Edge(identifier, sourceKeyFieldNames, destinationKeyFieldNames, definitionBody);
             workingSchema.edgeList.add(newEdge);
 
             // Update our edge label map.
             ArrayList<Edge> edgeList = new ArrayList<>();
             edgeList.add(newEdge);
-            edgeLabelMap.put(edgeLabel, edgeList);
+            edgeLabelMap.put(edgePatternLabel, edgeList);
             return newEdge;
         }
 
@@ -137,4 +145,27 @@
             DESTINATION_VERTEX_NOT_FOUND
         }
     }
+
+    private static class EdgeLabel {
+        public Set<ElementLabel> endpointLabels = new HashSet<>();
+        public ElementLabel edgeLabel = null;
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(edgeLabel, endpointLabels);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o instanceof EdgeLabel) {
+                EdgeLabel that = (EdgeLabel) o;
+                return Objects.equals(this.endpointLabels, that.endpointLabels)
+                        && Objects.equals(this.edgeLabel, that.edgeLabel);
+            }
+            return false;
+        }
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
index dd4a21e..e25df84 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
@@ -21,26 +21,28 @@
 import java.util.List;
 import java.util.Objects;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.VertexIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
 
 /**
  * Metadata representation of a vertex. A vertex consists of the following:
- * 1. A {@link GraphElementIdentifier}, to uniquely identify the vertex across other graph elements.
- * 2. A list of primary key fields, associated with the definition body.
- * 3. A SQL++ string denoting the definition body.
+ * <ul>
+ *  <li>A {@link VertexIdentifier}, to uniquely identify the vertex across other graph elements.</li>
+ *  <li>A list of primary key fields, associated with the definition body.</li>
+ *  <li>A SQL++ string denoting the definition body.</li>
+ * </ul>
  */
 public class Vertex implements IElement {
     private static final long serialVersionUID = 1L;
 
-    private final GraphElementIdentifier identifier;
+    private final VertexIdentifier identifier;
     private final List<List<String>> primaryKeyFieldNames;
     private final String definitionBody;
 
     /**
      * Use {@link Schema.Builder} to build Vertex instances instead of this constructor.
      */
-    Vertex(GraphElementIdentifier identifier, List<List<String>> primaryKeyFieldNames, String definitionBody) {
+    Vertex(VertexIdentifier identifier, List<List<String>> primaryKeyFieldNames, String definitionBody) {
         this.identifier = Objects.requireNonNull(identifier);
         this.primaryKeyFieldNames = primaryKeyFieldNames;
         this.definitionBody = Objects.requireNonNull(definitionBody);
@@ -51,13 +53,13 @@
     }
 
     @Override
-    public GraphElementIdentifier getIdentifier() {
+    public VertexIdentifier getIdentifier() {
         return identifier;
     }
 
     @Override
     public ElementLabel getLabel() {
-        return identifier.getElementLabel();
+        return identifier.getVertexLabel();
     }
 
     @Override
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
index 446ae27..fef8772 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
@@ -126,7 +126,7 @@
 
             // Read in the label name.
             IAObject labelNameObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_LABEL);
-            ElementLabel vertexLabel = new ElementLabel(((AString) labelNameObj).getStringValue());
+            ElementLabel elementLabel = new ElementLabel(((AString) labelNameObj).getStringValue(), false);
 
             // Read in the primary key fields.
             List<List<String>> primaryKeyFields = new ArrayList<>();
@@ -142,14 +142,14 @@
             String definitionBody = ((AString) bodyObj).getStringValue();
 
             // Read in the vertex definition, and perform validation of the metadata record.
-            schemaBuilder.addVertex(vertexLabel, primaryKeyFields, definitionBody);
+            schemaBuilder.addVertex(elementLabel, primaryKeyFields, definitionBody);
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
                     break;
 
                 case VERTEX_LABEL_CONFLICT:
                     throw new AsterixException(ErrorCode.METADATA_ERROR,
-                            "Conflicting vertex label found: " + vertexLabel);
+                            "Conflicting vertex label found: " + elementLabel);
 
                 default:
                     throw new AsterixException(ErrorCode.METADATA_ERROR,
@@ -165,15 +165,16 @@
 
             // Read in the label name.
             IAObject labelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_LABEL);
-            ElementLabel edgeLabel = new ElementLabel(((AString) labelNameObj).getStringValue());
+            ElementLabel edgeLabel = new ElementLabel(((AString) labelNameObj).getStringValue(), false);
 
             // Read in the destination label name.
             IAObject destinationLabelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_DESTINATION_LABEL);
-            ElementLabel destinationLabel = new ElementLabel(((AString) destinationLabelNameObj).getStringValue());
+            AString destinationLabelString = (AString) destinationLabelNameObj;
+            ElementLabel destinationLabel = new ElementLabel(destinationLabelString.getStringValue(), false);
 
             // Read in the source label name.
             IAObject sourceLabelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_SOURCE_LABEL);
-            ElementLabel sourceLabel = new ElementLabel(((AString) sourceLabelNameObj).getStringValue());
+            ElementLabel sourceLabel = new ElementLabel(((AString) sourceLabelNameObj).getStringValue(), false);
 
             // Read in the source key fields.
             List<List<String>> sourceKeyFields = new ArrayList<>();
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/AppendInternalPathDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/AppendInternalPathDescriptor.java
new file mode 100644
index 0000000..5b07204
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/AppendInternalPathDescriptor.java
@@ -0,0 +1,142 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator;
+
+import static org.apache.asterix.graphix.runtime.pointable.InternalPathPointable.HEADER_EDGE_LIST_END;
+import static org.apache.asterix.graphix.runtime.pointable.InternalPathPointable.HEADER_VERTEX_LIST_END;
+import static org.apache.asterix.graphix.runtime.pointable.InternalPathPointable.PATH_HEADER_LENGTH;
+import static org.apache.asterix.graphix.runtime.pointable.InternalPathPointable.PATH_SERIALIZED_TYPE_TAG;
+import static org.apache.asterix.graphix.runtime.pointable.SinglyLinkedListPointable.LIST_ITEM_LENGTH_SIZE;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.IntegerPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+/**
+ * Given a vertex (our first argument), an edge (our second argument), and an existing path (our third argument), create
+ * a new path that includes our given vertex and edge. This action is append-only, so we do not peer inside the existing
+ * vertex or edge lists.
+ */
+public class AppendInternalPathDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    // We
+    private MaterializeInternalPathCallbackFactory materializeInternalPathCallbackFactory;
+
+    @Override
+    public void setImmutableStates(Object... states) {
+        if (states != null) {
+            materializeInternalPathCallbackFactory = (MaterializeInternalPathCallbackFactory) states[0];
+        }
+    }
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new IScalarEvaluator() {
+                    private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
+                    private final DataOutput dataOutput = resultStorage.getDataOutput();
+
+                    private final IScalarEvaluator arg0Eval = args[0].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg1Eval = args[1].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg2Eval = args[2].createScalarEvaluator(ctx);
+                    private final IPointable arg0Ptr = new VoidPointable();
+                    private final IPointable arg1Ptr = new VoidPointable();
+                    private final IPointable arg2Ptr = new VoidPointable();
+
+                    @Override
+                    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+                        arg0Eval.evaluate(tuple, arg0Ptr);
+                        arg1Eval.evaluate(tuple, arg1Ptr);
+                        arg2Eval.evaluate(tuple, arg2Ptr);
+                        if (PointableHelper.checkAndSetMissingOrNull(result, arg0Ptr, arg1Ptr, arg2Ptr)) {
+                            return;
+                        }
+                        resultStorage.reset();
+
+                        try {
+                            // Build our path header. We start with our type tag.
+                            dataOutput.writeByte(PATH_SERIALIZED_TYPE_TAG);
+
+                            // Write the end offset of our new vertex list.
+                            int oldVertexLocalListEnd = IntegerPointable.getInteger(arg2Ptr.getByteArray(),
+                                    arg2Ptr.getStartOffset() + HEADER_VERTEX_LIST_END);
+                            int vertexItemSize = arg0Ptr.getLength() + LIST_ITEM_LENGTH_SIZE;
+                            dataOutput.writeInt(oldVertexLocalListEnd + vertexItemSize);
+                            //                            materializeInternalPathCallbackFactory
+
+                            // Write the end offset of our new edge list.
+                            int oldEdgeLocalListEnd = IntegerPointable.getInteger(arg2Ptr.getByteArray(),
+                                    arg2Ptr.getStartOffset() + HEADER_EDGE_LIST_END);
+                            int edgeItemSize = arg1Ptr.getLength() + LIST_ITEM_LENGTH_SIZE;
+                            dataOutput.writeInt(oldEdgeLocalListEnd + edgeItemSize);
+
+                            // Copy all of our old vertices.
+                            int oldVertexAbsoluteListStart = arg2Ptr.getStartOffset() + PATH_HEADER_LENGTH;
+                            dataOutput.write(arg2Ptr.getByteArray(), oldVertexAbsoluteListStart,
+                                    (oldVertexLocalListEnd + arg2Ptr.getStartOffset()) - oldVertexAbsoluteListStart);
+
+                            // Copy our new vertex.
+                            dataOutput.writeInt(arg0Ptr.getLength());
+                            dataOutput.write(arg0Ptr.getByteArray(), arg0Ptr.getStartOffset(), arg0Ptr.getLength());
+
+                            // Copy all of our new edges.
+                            int oldEdgeAbsoluteListStart = oldVertexLocalListEnd + arg2Ptr.getStartOffset();
+                            dataOutput.write(arg2Ptr.getByteArray(), oldEdgeAbsoluteListStart,
+                                    (oldEdgeLocalListEnd + arg2Ptr.getStartOffset()) - oldEdgeAbsoluteListStart);
+
+                            // Copy our new edge.
+                            dataOutput.writeInt(arg1Ptr.getLength());
+                            dataOutput.write(arg1Ptr.getByteArray(), arg1Ptr.getStartOffset(), arg1Ptr.getLength());
+
+                        } catch (IOException e) {
+                            throw HyracksDataException.create(e);
+                        }
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return GraphixFunctionIdentifiers.APPEND_INTERNAL_PATH;
+    }
+
+    public static final class MaterializeInternalPathCallbackFactory {
+        //        private Consumer<VoidPointable>
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/CreateInternalPathDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/CreateInternalPathDescriptor.java
new file mode 100644
index 0000000..08f9c78
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/CreateInternalPathDescriptor.java
@@ -0,0 +1,93 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.runtime.pointable.InternalPathPointable;
+import org.apache.asterix.graphix.runtime.pointable.SinglyLinkedListPointable;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+/**
+ * Create an initial path, which will consist of a single vertex (our argument) and zero edges.
+ */
+public class CreateInternalPathDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new IScalarEvaluator() {
+                    private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
+                    private final DataOutput dataOutput = resultStorage.getDataOutput();
+                    private final IScalarEvaluator arg0Eval = args[0].createScalarEvaluator(ctx);
+                    private final IPointable arg0Ptr = new VoidPointable();
+
+                    @Override
+                    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+                        arg0Eval.evaluate(tuple, arg0Ptr);
+                        if (PointableHelper.checkAndSetMissingOrNull(result, arg0Ptr)) {
+                            return;
+                        }
+                        resultStorage.reset();
+
+                        try {
+                            // Build our path header. We start with our type tag.
+                            dataOutput.writeByte(InternalPathPointable.PATH_SERIALIZED_TYPE_TAG);
+
+                            // Write the size of our vertex as a list item twice (to indicate we have no edges).
+                            int vertexItemSize = arg0Ptr.getLength() + SinglyLinkedListPointable.LIST_ITEM_LENGTH_SIZE;
+                            int startOfEdgeList = vertexItemSize + InternalPathPointable.PATH_HEADER_LENGTH;
+                            dataOutput.writeInt(startOfEdgeList);
+                            dataOutput.writeInt(startOfEdgeList);
+
+                            // Write our vertex item (size + the item itself).
+                            dataOutput.writeInt(vertexItemSize);
+                            dataOutput.write(arg0Ptr.getByteArray(), arg0Ptr.getStartOffset(), arg0Ptr.getLength());
+
+                        } catch (IOException e) {
+                            throw HyracksDataException.create(e);
+                        }
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return GraphixFunctionIdentifiers.CREATE_INTERNAL_PATH;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEdgeDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEdgeDescriptor.java
new file mode 100644
index 0000000..9c03938
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEdgeDescriptor.java
@@ -0,0 +1,99 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator;
+
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.runtime.evaluator.common.AbstractElementCompareEvaluator;
+import org.apache.asterix.graphix.runtime.pointable.InternalPathPointable;
+import org.apache.asterix.graphix.runtime.pointable.SinglyLinkedListPointable;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.asterix.runtime.exceptions.TypeMismatchException;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+/**
+ * For a given tuple (edge, path) pair, return true if there are no duplicate edges in the given path (and false
+ * otherwise). This function is used internally to enforce no-repeated-edge navigation semantics.
+ */
+public class IsDistinctEdgeDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new AbstractElementCompareEvaluator() {
+                    private final IScalarEvaluator arg0Eval = args[0].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg1Eval = args[1].createScalarEvaluator(ctx);
+                    private final IPointable arg0Ptr = new VoidPointable();
+                    private final IPointable arg1Ptr = new VoidPointable();
+
+                    @Override
+                    protected boolean readTuple(IFrameTupleReference tuple, IPointable result)
+                            throws HyracksDataException {
+                        arg0Eval.evaluate(tuple, arg0Ptr);
+                        arg1Eval.evaluate(tuple, arg1Ptr);
+
+                        // If our edge or path is NULL or MISSING, then our result is NULL / MISSING.
+                        if (PointableHelper.checkAndSetMissingOrNull(result, arg0Ptr, arg1Ptr)) {
+                            return false;
+                        }
+
+                        // Ensure that we have a path (i.e. bit-array) as our second argument.
+                        byte typeTagByte = arg1Ptr.getByteArray()[arg1Ptr.getStartOffset()];
+                        if (typeTagByte != InternalPathPointable.PATH_SERIALIZED_TYPE_TAG) {
+                            throw new TypeMismatchException(sourceLoc, getIdentifier(), 1, typeTagByte,
+                                    InternalPathPointable.PATH_SERIALIZED_TYPE_TAG);
+                        }
+
+                        edgeListItemCallback.getInputItemPtr().set(arg0Ptr);
+                        pathPtr.set(arg1Ptr);
+                        return true;
+                    }
+
+                    @Override
+                    protected boolean compare() {
+                        SinglyLinkedListPointable<Boolean> edgeListPointable = pathPtr.getEdgeListPointable();
+                        while (edgeListPointable.hasNext()) {
+                            if (edgeListPointable.next()) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return GraphixFunctionIdentifiers.IS_DISTINCT_EDGE;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEverythingDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEverythingDescriptor.java
new file mode 100644
index 0000000..b3dda6e
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctEverythingDescriptor.java
@@ -0,0 +1,109 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator;
+
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.runtime.evaluator.common.AbstractElementCompareEvaluator;
+import org.apache.asterix.graphix.runtime.pointable.InternalPathPointable;
+import org.apache.asterix.graphix.runtime.pointable.SinglyLinkedListPointable;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.asterix.runtime.exceptions.TypeMismatchException;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+/**
+ * For a given tuple (vertex, edge, path) triple, return true if there are no duplicate vertices or edges in the given
+ * path (and false otherwise). This function is used internally to enforce no-repeated-everything navigation semantics.
+ */
+public class IsDistinctEverythingDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new AbstractElementCompareEvaluator() {
+                    private final IScalarEvaluator arg0Eval = args[0].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg1Eval = args[1].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg2Eval = args[2].createScalarEvaluator(ctx);
+                    private final IPointable arg0Ptr = new VoidPointable();
+                    private final IPointable arg1Ptr = new VoidPointable();
+                    private final IPointable arg2Ptr = new VoidPointable();
+
+                    @Override
+                    protected boolean readTuple(IFrameTupleReference tuple, IPointable result)
+                            throws HyracksDataException {
+                        arg0Eval.evaluate(tuple, arg0Ptr);
+                        arg1Eval.evaluate(tuple, arg1Ptr);
+                        arg2Eval.evaluate(tuple, arg2Ptr);
+
+                        // If our edge, vertex, or path is NULL or MISSING, then our result is NULL / MISSING.
+                        if (PointableHelper.checkAndSetMissingOrNull(result, arg0Ptr, arg1Ptr, arg2Ptr)) {
+                            return false;
+                        }
+
+                        // Ensure that we have a path (i.e. bit-array) as our third argument.
+                        byte typeTagByte = arg2Ptr.getByteArray()[arg2Ptr.getStartOffset()];
+                        if (typeTagByte != InternalPathPointable.PATH_SERIALIZED_TYPE_TAG) {
+                            throw new TypeMismatchException(sourceLoc, getIdentifier(), 2, typeTagByte,
+                                    InternalPathPointable.PATH_SERIALIZED_TYPE_TAG);
+                        }
+
+                        vertexListItemCallback.getInputItemPtr().set(arg0Ptr);
+                        edgeListItemCallback.getInputItemPtr().set(arg1Ptr);
+                        pathPtr.set(arg2Ptr);
+                        return true;
+                    }
+
+                    @Override
+                    protected boolean compare() {
+                        SinglyLinkedListPointable<Boolean> vertexListPointable = pathPtr.getVertexListPointable();
+                        SinglyLinkedListPointable<Boolean> edgeListPointable = pathPtr.getEdgeListPointable();
+                        while (vertexListPointable.hasNext()) {
+                            if (vertexListPointable.next()) {
+                                return true;
+                            }
+                        }
+                        while (edgeListPointable.hasNext()) {
+                            if (edgeListPointable.next()) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return GraphixFunctionIdentifiers.IS_DISTINCT_EVERYTHING;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctVertexDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctVertexDescriptor.java
new file mode 100644
index 0000000..160837e
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/IsDistinctVertexDescriptor.java
@@ -0,0 +1,99 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator;
+
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.runtime.evaluator.common.AbstractElementCompareEvaluator;
+import org.apache.asterix.graphix.runtime.pointable.InternalPathPointable;
+import org.apache.asterix.graphix.runtime.pointable.SinglyLinkedListPointable;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.asterix.runtime.exceptions.TypeMismatchException;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+/**
+ * For a given tuple (vertex, path) pair, return true if there are no duplicate vertices in the given path (and false
+ * otherwise). This function is used internally to enforce no-repeated-vertex navigation semantics.
+ */
+public class IsDistinctVertexDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public IScalarEvaluatorFactory createEvaluatorFactory(IScalarEvaluatorFactory[] args) {
+        return new IScalarEvaluatorFactory() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+                return new AbstractElementCompareEvaluator() {
+                    private final IScalarEvaluator arg0Eval = args[0].createScalarEvaluator(ctx);
+                    private final IScalarEvaluator arg1Eval = args[1].createScalarEvaluator(ctx);
+                    private final IPointable arg0Ptr = new VoidPointable();
+                    private final IPointable arg1Ptr = new VoidPointable();
+
+                    @Override
+                    protected boolean readTuple(IFrameTupleReference tuple, IPointable result)
+                            throws HyracksDataException {
+                        arg0Eval.evaluate(tuple, arg0Ptr);
+                        arg1Eval.evaluate(tuple, arg1Ptr);
+
+                        // If our vertex or path is NULL or MISSING, then our result is NULL / MISSING.
+                        if (PointableHelper.checkAndSetMissingOrNull(result, arg0Ptr, arg1Ptr)) {
+                            return false;
+                        }
+
+                        // Ensure that we have a path (i.e. bit-array) as our second argument.
+                        byte typeTagByte = arg1Ptr.getByteArray()[arg1Ptr.getStartOffset()];
+                        if (typeTagByte != InternalPathPointable.PATH_SERIALIZED_TYPE_TAG) {
+                            throw new TypeMismatchException(sourceLoc, getIdentifier(), 1, typeTagByte,
+                                    InternalPathPointable.PATH_SERIALIZED_TYPE_TAG);
+                        }
+
+                        vertexListItemCallback.getInputItemPtr().set(arg0Ptr);
+                        pathPtr.set(arg1Ptr);
+                        return true;
+                    }
+
+                    @Override
+                    protected boolean compare() {
+                        SinglyLinkedListPointable<Boolean> vertexListPointable = pathPtr.getVertexListPointable();
+                        while (vertexListPointable.hasNext()) {
+                            if (vertexListPointable.next()) {
+                                return true;
+                            }
+                        }
+                        return false;
+                    }
+                };
+            }
+        };
+    }
+
+    @Override
+    public FunctionIdentifier getIdentifier() {
+        return GraphixFunctionIdentifiers.IS_DISTINCT_VERTEX;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/common/AbstractElementCompareEvaluator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/common/AbstractElementCompareEvaluator.java
new file mode 100644
index 0000000..7185b16
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/evaluator/common/AbstractElementCompareEvaluator.java
@@ -0,0 +1,85 @@
+/*
+ * 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.asterix.graphix.runtime.evaluator.common;
+
+import java.io.DataOutput;
+import java.util.function.Function;
+
+import org.apache.asterix.dataflow.data.nontagged.serde.AObjectSerializerDeserializer;
+import org.apache.asterix.graphix.runtime.pointable.InternalPathPointable;
+import org.apache.asterix.om.base.ABoolean;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.data.std.util.DataUtils;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+
+public abstract class AbstractElementCompareEvaluator implements IScalarEvaluator {
+    protected final AObjectSerializerDeserializer serde = AObjectSerializerDeserializer.INSTANCE;
+    protected final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
+    protected final DataOutput dataOutput = resultStorage.getDataOutput();
+
+    protected final ListItemCompareCallback vertexListItemCallback = new ListItemCompareCallback();
+    protected final ListItemCompareCallback edgeListItemCallback = new ListItemCompareCallback();
+    protected final InternalPathPointable<Boolean> pathPtr =
+            new InternalPathPointable<>(vertexListItemCallback, edgeListItemCallback);
+
+    @Override
+    public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+        if (!readTuple(tuple, result)) {
+            return;
+        }
+
+        if (compare()) {
+            // We have found a duplicate item. Exit and return false.
+            resultStorage.reset();
+            serde.serialize(ABoolean.FALSE, dataOutput);
+            result.set(resultStorage);
+        }
+
+        // No duplicate items have been found. Return true.
+        resultStorage.reset();
+        serde.serialize(ABoolean.TRUE, dataOutput);
+        result.set(resultStorage);
+    }
+
+    /**
+     * @return True if we should proceed (and result has been set). False otherwise.
+     */
+    protected abstract boolean readTuple(IFrameTupleReference tuple, IPointable result) throws HyracksDataException;
+
+    protected abstract boolean compare() throws HyracksDataException;
+
+    // We treat vertices / edges as black-boxes, we do not know their contents. We compare blindly.
+    public static final class ListItemCompareCallback implements Function<VoidPointable, Boolean> {
+        private final VoidPointable inputItemPtr = new VoidPointable();
+
+        public VoidPointable getInputItemPtr() {
+            return inputItemPtr;
+        }
+
+        @Override
+        public Boolean apply(VoidPointable listItemPtr) {
+            return DataUtils.equals(inputItemPtr, listItemPtr);
+        }
+    }
+
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionInfoCollection.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionInfoCollection.java
new file mode 100644
index 0000000..acdaf26
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionInfoCollection.java
@@ -0,0 +1,79 @@
+/*
+ * 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.asterix.graphix.runtime.function;
+
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.APPEND_INTERNAL_PATH;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.CREATE_INTERNAL_PATH;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.IS_DISTINCT_EDGE;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.IS_DISTINCT_EVERYTHING;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.IS_DISTINCT_VERTEX;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.MATERIALIZE_PATH;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.graphix.type.MaterializePathTypeComputer;
+import org.apache.asterix.om.functions.FunctionInfo;
+import org.apache.asterix.om.typecomputer.base.AbstractResultTypeComputer;
+import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
+import org.apache.asterix.om.typecomputer.impl.ABooleanTypeComputer;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.functions.IFunctionInfo;
+
+public class GraphixFunctionInfoCollection {
+    private static final Map<FunctionIdentifier, IFunctionInfo> functionInfoMap = new HashMap<>();
+
+    static {
+        // The following functions yield boolean values.
+        final IResultTypeComputer booleanComputer = ABooleanTypeComputer.INSTANCE_NULLABLE;
+        functionInfoMap.put(IS_DISTINCT_VERTEX, new GraphixFunctionInfo(IS_DISTINCT_VERTEX, booleanComputer));
+        functionInfoMap.put(IS_DISTINCT_EDGE, new GraphixFunctionInfo(IS_DISTINCT_EDGE, booleanComputer));
+        functionInfoMap.put(IS_DISTINCT_EVERYTHING, new GraphixFunctionInfo(IS_DISTINCT_EVERYTHING, booleanComputer));
+
+        // The following functions yield raw path values (we hijack the bitarray type here).
+        final IResultTypeComputer rawPathComputer = new AbstractResultTypeComputer() {
+            @Override
+            protected IAType getResultType(ILogicalExpression expr, IAType... strippedInputTypes) {
+                return BuiltinType.ABITARRAY;
+            }
+        };
+        functionInfoMap.put(CREATE_INTERNAL_PATH, new GraphixFunctionInfo(CREATE_INTERNAL_PATH, rawPathComputer));
+        functionInfoMap.put(APPEND_INTERNAL_PATH, new GraphixFunctionInfo(APPEND_INTERNAL_PATH, rawPathComputer));
+
+        // The following function yields a closed record value.
+        final IResultTypeComputer pathComputer = MaterializePathTypeComputer.INSTANCE;
+        functionInfoMap.put(MATERIALIZE_PATH, new GraphixFunctionInfo(MATERIALIZE_PATH, pathComputer));
+    }
+
+    public static IFunctionInfo getFunctionInfo(FunctionIdentifier functionIdentifier) {
+        return functionInfoMap.get(functionIdentifier);
+    }
+
+    public static class GraphixFunctionInfo extends FunctionInfo {
+        private static final long serialVersionUID = 1L;
+
+        public GraphixFunctionInfo(FunctionIdentifier functionIdentifier, IResultTypeComputer typeComputer) {
+            // We only have functional functions.
+            super(functionIdentifier, typeComputer, true);
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionRegistrant.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionRegistrant.java
new file mode 100644
index 0000000..983165b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/function/GraphixFunctionRegistrant.java
@@ -0,0 +1,59 @@
+/*
+ * 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.asterix.graphix.runtime.function;
+
+import org.apache.asterix.graphix.runtime.evaluator.AppendInternalPathDescriptor;
+import org.apache.asterix.graphix.runtime.evaluator.CreateInternalPathDescriptor;
+import org.apache.asterix.graphix.runtime.evaluator.IsDistinctEdgeDescriptor;
+import org.apache.asterix.graphix.runtime.evaluator.IsDistinctEverythingDescriptor;
+import org.apache.asterix.graphix.runtime.evaluator.IsDistinctVertexDescriptor;
+import org.apache.asterix.om.functions.IFunctionCollection;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.om.functions.IFunctionRegistrant;
+import org.apache.asterix.om.functions.IFunctionTypeInferer;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+
+public class GraphixFunctionRegistrant implements IFunctionRegistrant {
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public void register(IFunctionCollection fc) {
+        fc.add(IsDistinctVertexDescriptor::new);
+        fc.add(IsDistinctEdgeDescriptor::new);
+        fc.add(IsDistinctEverythingDescriptor::new);
+        fc.add(CreateInternalPathDescriptor::new);
+
+        // We have a callback-factory we need to pass to our APPEND-INTERNAL-PATH evaluator-factory.
+        fc.add(new IFunctionDescriptorFactory() {
+            @Override
+            public IFunctionTypeInferer createFunctionTypeInferer() {
+                return (expr, fd, context, compilerProps) -> {
+                    AbstractFunctionCallExpression funcCallExpr = (AbstractFunctionCallExpression) expr;
+                    fd.setImmutableStates(funcCallExpr.getOpaqueParameters()[0]);
+                };
+            }
+
+            @Override
+            public IFunctionDescriptor createFunctionDescriptor() {
+                return new AppendInternalPathDescriptor();
+            }
+        });
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/InternalPathPointable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/InternalPathPointable.java
new file mode 100644
index 0000000..2fcf96c
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/InternalPathPointable.java
@@ -0,0 +1,80 @@
+/*
+ * 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.asterix.graphix.runtime.pointable;
+
+import java.util.function.Function;
+
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.hyracks.data.std.api.AbstractPointable;
+import org.apache.hyracks.data.std.primitive.IntegerPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+
+/**
+ * A path consists of a header, a list of vertices, and a list of edges.
+ * <p>
+ * <ol>
+ * <li>A path header consists of 9 bytes, and is formatted as such:
+ *  <pre>
+ *    [bit-array type tag]      -- 1 byte
+ *    [vertex-list end offset]  -- 4 bytes
+ *    [edge-list end offset]    -- 4 bytes
+ *  </pre></li>
+ *  <li>A path's list of vertices starts at 8 bytes and ends at [vertex-list end offset] bytes.</li>
+ *  <li>A path's list of edges starts at [vertex-list end offset] bytes and ends at [edge-list end offset] bytes. All
+ *  offsets are given from the start of the path itself (not absolute, from the containing byte array).</li>
+ * </ol>
+ */
+public class InternalPathPointable<T> extends AbstractPointable {
+    public static final int HEADER_VERTEX_LIST_END = 1;
+    public static final int HEADER_EDGE_LIST_END = 5;
+    public static final int PATH_HEADER_LENGTH = 9;
+
+    // TODO (GLENN): Create a custom type tag specifically for extensions.
+    public static final byte PATH_SERIALIZED_TYPE_TAG = ATypeTag.BITARRAY.serialize();
+
+    // We will set the following on invocation of our "set".
+    private final SinglyLinkedListPointable<T> edgeListPointable;
+    private final SinglyLinkedListPointable<T> vertexListPointable;
+
+    public InternalPathPointable(Function<VoidPointable, T> vertexListItemCallback,
+            Function<VoidPointable, T> edgeListItemCallback) {
+        this.vertexListPointable = new SinglyLinkedListPointable<>(vertexListItemCallback);
+        this.edgeListPointable = new SinglyLinkedListPointable<>(edgeListItemCallback);
+    }
+
+    @Override
+    public void set(byte[] bytes, int start, int length) {
+        int absoluteStartOfVertices = start + PATH_HEADER_LENGTH;
+        int localEndOfVertices = IntegerPointable.getInteger(bytes, start + HEADER_VERTEX_LIST_END);
+        int localEndOfEdges = IntegerPointable.getInteger(bytes, start + HEADER_EDGE_LIST_END);
+        int absoluteEndOfVertices = start + localEndOfVertices;
+        int absoluteEndOfEdges = start + localEndOfEdges;
+        vertexListPointable.set(bytes, absoluteStartOfVertices, absoluteEndOfVertices - absoluteStartOfVertices);
+        edgeListPointable.set(bytes, absoluteEndOfVertices, absoluteEndOfEdges - absoluteEndOfVertices);
+        super.set(bytes, start, length);
+    }
+
+    public SinglyLinkedListPointable<T> getEdgeListPointable() {
+        return edgeListPointable;
+    }
+
+    public SinglyLinkedListPointable<T> getVertexListPointable() {
+        return vertexListPointable;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/SinglyLinkedListPointable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/SinglyLinkedListPointable.java
new file mode 100644
index 0000000..6ceb0ae
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/runtime/pointable/SinglyLinkedListPointable.java
@@ -0,0 +1,73 @@
+/*
+ * 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.asterix.graphix.runtime.pointable;
+
+import java.util.NoSuchElementException;
+import java.util.function.Function;
+
+import org.apache.hyracks.data.std.api.AbstractPointable;
+import org.apache.hyracks.data.std.primitive.IntegerPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+
+/**
+ * A lean representation of a singly-linked list, where each item in the list only has a 4-byte length. We can only
+ * access this SLL from start to finish, as we do not know the size of each item (nor the number of items) apriori.
+ */
+public class SinglyLinkedListPointable<T> extends AbstractPointable {
+    public static final int LIST_ITEM_LENGTH_SIZE = 4;
+
+    private final Function<VoidPointable, T> listItemConsumer;
+    private final VoidPointable listItemPointable;
+
+    // This is a **stateful** pointable, whose action is dictated by the given list-item callback.
+    private int currentPosition;
+
+    public SinglyLinkedListPointable(Function<VoidPointable, T> listItemConsumer) {
+        this.listItemPointable = new VoidPointable();
+        this.listItemConsumer = listItemConsumer;
+    }
+
+    @Override
+    public void set(byte[] bytes, int start, int length) {
+        super.set(bytes, start, length);
+        currentPosition = 0;
+    }
+
+    public boolean hasNext() {
+        return currentPosition < length;
+    }
+
+    public T next() {
+        if (!hasNext()) {
+            throw new NoSuchElementException();
+        }
+
+        // Determine the length of our working item.
+        int itemLength = IntegerPointable.getInteger(bytes, start + currentPosition);
+        currentPosition = currentPosition + LIST_ITEM_LENGTH_SIZE;
+
+        // Consume our list item.
+        listItemPointable.set(bytes, currentPosition, itemLength);
+        T result = listItemConsumer.apply(listItemPointable);
+
+        // Advance our cursor.
+        currentPosition = currentPosition + itemLength;
+        return result;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/type/MaterializePathTypeComputer.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/type/MaterializePathTypeComputer.java
new file mode 100644
index 0000000..52e3be5
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/type/MaterializePathTypeComputer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.asterix.graphix.type;
+
+import org.apache.asterix.om.typecomputer.base.AbstractResultTypeComputer;
+import org.apache.asterix.om.typecomputer.base.IResultTypeComputer;
+import org.apache.asterix.om.types.AOrderedListType;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.IAType;
+import org.apache.asterix.om.utils.RecordUtil;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+
+public class MaterializePathTypeComputer extends AbstractResultTypeComputer {
+    public static final String VERTICES_FIELD_NAME = "Vertices";
+    public static final String EDGES_FIELD_NAME = "Edges";
+    public static final ARecordType TYPE;
+    static {
+        String typeName = "materializedPathType";
+        String[] fieldNames = new String[] { VERTICES_FIELD_NAME, EDGES_FIELD_NAME };
+        AOrderedListType listType = new AOrderedListType(RecordUtil.FULLY_OPEN_RECORD_TYPE, null);
+        TYPE = new ARecordType(typeName, fieldNames, new IAType[] { listType, listType }, false);
+    }
+
+    // Our type computer will always return the type above (while respecting NULL/MISSING IN -> OUT semantics).
+    public static final IResultTypeComputer INSTANCE = new MaterializePathTypeComputer();
+
+    @Override
+    protected IAType getResultType(ILogicalExpression expr, IAType... strippedInputTypes) {
+        return TYPE;
+    }
+}
diff --git a/asterix-graphix/src/main/resources/META-INF/services/org.apache.asterix.om.functions.IFunctionRegistrant b/asterix-graphix/src/main/resources/META-INF/services/org.apache.asterix.om.functions.IFunctionRegistrant
new file mode 100644
index 0000000..5ee5a83
--- /dev/null
+++ b/asterix-graphix/src/main/resources/META-INF/services/org.apache.asterix.om.functions.IFunctionRegistrant
@@ -0,0 +1,20 @@
+#
+# 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.
+#
+
+org.apache.asterix.graphix.runtime.function.GraphixFunctionRegistrant
\ No newline at end of file
diff --git a/asterix-graphix/src/main/resources/cc.conf b/asterix-graphix/src/main/resources/cc.conf
index ae068ca..968c07c 100644
--- a/asterix-graphix/src/main/resources/cc.conf
+++ b/asterix-graphix/src/main/resources/cc.conf
@@ -32,7 +32,13 @@
 log.level=INFO
 
 [extension/org.apache.asterix.graphix.extension.GraphixQueryTranslatorExtension]
-enabled=true
+graphix.evaluation.default=expand-and-union
+graphix.semantics.pattern=isomorphism
+graphix.semantics.navigation=no-repeat-anything
+graphix.schema-decorate.vertex=as-needed
+graphix.schema-decorate.edge=as-needed
+
+; We use dummy keys for the extension sections below.
 [extension/org.apache.asterix.graphix.extension.GraphixLangExtension]
 enabled=true
 [extension/org.apache.asterix.graphix.extension.GraphixMetadataExtension]
diff --git a/asterix-graphix/src/main/resources/lang-extension/lang.txt b/asterix-graphix/src/main/resources/lang-extension/lang.txt
index 99952bf..ca7aa66 100644
--- a/asterix-graphix/src/main/resources/lang-extension/lang.txt
+++ b/asterix-graphix/src/main/resources/lang-extension/lang.txt
@@ -18,17 +18,19 @@
 //
 
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.IElementIdentifier;
+import org.apache.asterix.graphix.lang.annotation.ElementEvaluationAnnotation;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 import org.apache.asterix.graphix.lang.optype.MatchType;
+import org.apache.asterix.graphix.lang.parser.GraphixParserHint;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
@@ -40,7 +42,7 @@
 import org.apache.asterix.lang.sqlpp.parser.Token;
 
 @new_at_the_class_def
-public GraphElementDeclaration parseGraphElementBody(GraphElementIdentifier identifier) throws CompilationException {
+public GraphElementDeclaration parseGraphElementBody(IElementIdentifier identifier) throws CompilationException {
     return parseImpl(new ParseFunction<GraphElementDeclaration>() {
         @Override
         public GraphElementDeclaration parse() throws ParseException {
@@ -58,6 +60,35 @@
     });
 }
 
+@new_at_the_class_def
+public GraphixParserHint fetchGraphixHint(Token token, GraphixParserHint[] expectedHints) {
+    if (token == null) {
+      return null;
+    }
+    Token hintToken = token.specialToken;
+
+    // We have a hint. Pull this from our collector.
+    if (hintToken == null) {
+      return null;
+    }
+    SourceLocation sourceLoc = getSourceLocation(hintToken);
+    hintCollector.remove(sourceLoc);
+
+    // Check for Graphix hints.
+    if (hintToken.hint != null) {
+        // A Graphix hint is not a SQLPP hint, so we shouldn't get anything in the "hint" field.
+        warnUnexpectedHint(hintToken.hint.getIdentifier(), sourceLoc, StringUtil.join(expectedHints, ", ", "\""));
+
+    } else if (hintToken.hintParams != null) {
+        for (GraphixParserHint graphixHint : expectedHints) {
+            if (graphixHint.getId().equals(hintToken.hintParams)) {
+                return graphixHint;
+            }
+        }
+    }
+    return null;
+}
+
 @override
 SelectBlock SelectBlock() throws ParseException:
 {
@@ -146,8 +177,8 @@
       return selectBlock;
 
     } else {
-      GraphSelectBlock selectBlock = new GraphSelectBlock(selectClause, fromGraphClause,
-        fromLetWhereClauses, groupbyClause, gbyLetHavingClauses);
+      SelectBlock selectBlock = new SelectBlock(selectClause, fromGraphClause, fromLetWhereClauses,
+        groupbyClause, gbyLetHavingClauses);
       selectBlock.setSourceLocation(startSrcLoc);
       return selectBlock;
     }
@@ -389,11 +420,11 @@
   Token beginPos = null, endPos = null;
   int positionOffset = 0;
   Expression vertexDefinitionExpr;
-  ElementLabel vertexLabel;
+  ElementLabel elementLabel;
 }
 {
   <VERTEX>
-  vertexLabel = GraphVertexDefinitionPattern()
+  elementLabel = GraphVertexDefinitionPattern()
   <PRIMARY> <KEY> <LEFTPAREN> primaryKeyFields = KeyFields() <RIGHTPAREN>
   <AS>
   {
@@ -408,7 +439,7 @@
     String vDef = extractFragment(beginPos.beginLine, beginPos.beginColumn + positionOffset, endPos.endLine,
       endPos.endColumn + 1);
     removeCurrentScope();
-    GraphConstructor.VertexConstructor vertexConstructor = new GraphConstructor.VertexConstructor(vertexLabel,
+    GraphConstructor.VertexConstructor vertexConstructor = new GraphConstructor.VertexConstructor(elementLabel,
       primaryKeyFields.second, primaryKeyFields.first, vertexDefinitionExpr, vDef);
     return addSourceLocation(vertexConstructor, startStmtToken);
   }
@@ -422,7 +453,7 @@
 {
   <LEFTPAREN> <COLON> vertexName = Identifier() <RIGHTPAREN>
   {
-    return new ElementLabel(vertexName);
+    return new ElementLabel(vertexName, false);
   }
 }
 
@@ -494,18 +525,18 @@
 @new
 Pair<Triple<ElementLabel, ElementLabel, ElementLabel>, Boolean> GraphEdgeDefinitionPattern() throws ParseException:
 {
-  ElementLabel leftVertexLabel, rightVertexLabel;
+  ElementLabel leftElementLabel, rightElementLabel;
   boolean isDirectedLeft;
   String edgeName;
 }
 {
-  leftVertexLabel = GraphVertexDefinitionPattern()
+  leftElementLabel = GraphVertexDefinitionPattern()
   ( <MINUS> <LEFTBRACKET> <COLON> edgeName = Identifier() <RIGHTBRACKET> <MINUS> <GT> { isDirectedLeft = false; }
   | <LT> <MINUS> <LEFTBRACKET> <COLON> edgeName = Identifier() <RIGHTBRACKET> <MINUS> { isDirectedLeft = true; } )
-  rightVertexLabel = GraphVertexDefinitionPattern()
+  rightElementLabel = GraphVertexDefinitionPattern()
   {
     Triple<ElementLabel, ElementLabel, ElementLabel> t = new Triple<ElementLabel, ElementLabel, ElementLabel>(
-      leftVertexLabel, new ElementLabel(edgeName), rightVertexLabel);
+      leftElementLabel, new ElementLabel(edgeName, false), rightElementLabel);
     return new Pair<Triple<ElementLabel, ElementLabel, ElementLabel>, Boolean>(t, isDirectedLeft);
   }
 }
@@ -603,7 +634,7 @@
 
   Token startToken = null, edgeStartToken = null;
   VertexPatternExpr vertexExpr = null;
-  EdgeDescriptor edgeDescriptor = null;
+  Pair<EdgeDescriptor, List<IExpressionAnnotation>> edgeDescriptorPair = null;
 }
 {
   vertexExpr = VertexPatternExpression()
@@ -612,11 +643,16 @@
     orderedVertexExpressions.add(vertexExpr);
   }
   (
-    edgeDescriptor = EdgeDescriptor() { edgeStartToken = token; }
+    edgeDescriptorPair = EdgeDescriptorPair() { edgeStartToken = token; }
     vertexExpr = VertexPatternExpression()
     {
       VertexPatternExpr leftVertex = orderedVertexExpressions.get(orderedVertexExpressions.size() - 1);
-      EdgePatternExpr edgePattern = new EdgePatternExpr(leftVertex, vertexExpr, edgeDescriptor);
+      EdgePatternExpr edgePattern = new EdgePatternExpr(leftVertex, vertexExpr, edgeDescriptorPair.first);
+      if (!edgeDescriptorPair.second.isEmpty()) {
+        for (IExpressionAnnotation annotation : edgeDescriptorPair.second) {
+          edgePattern.addHint(annotation);
+        }
+      }
       orderedEdgeExpressions.add(addSourceLocation(edgePattern, edgeStartToken));
       orderedVertexExpressions.add(vertexExpr);
     }
@@ -630,33 +666,44 @@
 @new
 VertexPatternExpr VertexPatternExpression() throws ParseException:
 {
-  Set<ElementLabel> vertexLabels = new HashSet<ElementLabel>();
+  Set<String> vertexLabels = new HashSet<String>();
   VariableExpr variableExpr = null;
+  Expression filterExpr = null;
+  boolean isNegatedLabelSet = false;
   Token startToken = null;
   String vertexLabelName;
 }
 {
   <LEFTPAREN> { startToken = token; }
+  ( variableExpr = Variable() )?
   (
-    variableExpr = Variable()
+    <COLON>
+    ( <CARET> { isNegatedLabelSet = true; } )?
+    vertexLabels = ParenthesizedLabelValueSet()
   )?
-  (
-    <COLON> vertexLabelName = Identifier() { vertexLabels.add(new ElementLabel(vertexLabelName)); }
-    ( <BAR> vertexLabelName = Identifier() { vertexLabels.add(new ElementLabel(vertexLabelName)); } )*
-  )?
+  ( <WHERE> filterExpr = Expression() )?
   <RIGHTPAREN>
   {
-    VertexPatternExpr vertexExpression = new VertexPatternExpr(variableExpr, vertexLabels);
+    // Construct our label set.
+    Set<ElementLabel> labels = new HashSet<ElementLabel>();
+    for (String vertexLabelValue : vertexLabels) {
+      labels.add(new ElementLabel(vertexLabelValue, isNegatedLabelSet));
+    }
+    VertexPatternExpr vertexExpression = new VertexPatternExpr(variableExpr, filterExpr, labels);
     return addSourceLocation(vertexExpression, startToken);
   }
 }
 
 @new
-EdgeDescriptor EdgeDescriptor() throws ParseException:
+Pair<EdgeDescriptor, List<IExpressionAnnotation>> EdgeDescriptorPair() throws ParseException:
 {
-  Pair<Set<ElementLabel>, Pair<Integer, Integer>> edgeDetail = null;
-  Token startToken = null;
+  GraphixParserHint[] expectedHints = { GraphixParserHint.EXPAND_AND_UNION_HINT,
+                                        GraphixParserHint.SWITCH_AND_CYCLE_HINT };
+  List<IExpressionAnnotation> hintList = new ArrayList<IExpressionAnnotation>();
+  Triple<Set<ElementLabel>, Pair<Integer, Integer>, Expression> edgeDetail = null;
+  Token startToken = null, hintToken = null;
   VariableExpr edgeVariable = null;
+  Expression filterExpr = null;
 
   // We default to undirected edges.
   EdgeDescriptor.EdgeDirection edgeDirection = EdgeDescriptor.EdgeDirection.UNDIRECTED;
@@ -666,11 +713,9 @@
     <MINUS> { startToken = token; }
     (
       <LEFTBRACKET>
-      (
-        edgeVariable = Variable()
-      )?
-      ( <COLON> edgeDetail = EdgeDetail() )?
-      <RIGHTBRACKET> <MINUS>
+      ( edgeVariable = Variable() )?
+      ( edgeDetail = EdgeDetail() )?
+      <RIGHTBRACKET> { hintToken = token; } <MINUS>
     )?
     ( <GT> { edgeDirection = EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT; } )?
     |
@@ -681,14 +726,21 @@
     <MINUS>
     (
       <LEFTBRACKET>
-      (
-        edgeVariable = Variable()
-      )?
-      ( <COLON> edgeDetail = EdgeDetail() )?
-      <RIGHTBRACKET> <MINUS>
+      ( edgeVariable = Variable() )?
+      ( edgeDetail = EdgeDetail() )?
+      <RIGHTBRACKET> { hintToken = token; } <MINUS>
     )?
   )
   {
+    // See if we have an evaluation hint.
+    GraphixParserHint evalHint = fetchGraphixHint(hintToken, expectedHints);
+    if (Objects.equals(evalHint, GraphixParserHint.EXPAND_AND_UNION_HINT)) {
+      hintList.add(new ElementEvaluationAnnotation(ElementEvaluationAnnotation.Kind.EXPAND_AND_UNION));
+
+    } else if (Objects.equals(evalHint, GraphixParserHint.SWITCH_AND_CYCLE_HINT)) {
+      hintList.add(new ElementEvaluationAnnotation(ElementEvaluationAnnotation.Kind.SWITCH_AND_CYCLE));
+    }
+
     // Edges (by default) are of pattern type EDGE and are not sub-paths.
     EdgeDescriptor.PatternType patternType = EdgeDescriptor.PatternType.EDGE;
     Integer hopCountMin = 1;
@@ -697,6 +749,7 @@
     Set<ElementLabel> labels = new HashSet<ElementLabel>();
     if (edgeDetail != null) {
       labels = edgeDetail.first;
+      filterExpr = edgeDetail.third;
 
       // We have explicitly specified "{" and "}". Use sub-path semantics.
       if (edgeDetail.second != null) {
@@ -705,42 +758,71 @@
         hopCountMax = edgeDetail.second.second;
       }
     }
-
-    return new EdgeDescriptor(edgeDirection, patternType, labels, edgeVariable, hopCountMin, hopCountMax);
+    EdgeDescriptor edgeDescriptor =
+      new EdgeDescriptor(edgeDirection, patternType, labels, filterExpr, edgeVariable, hopCountMin, hopCountMax);
+    return new Pair<EdgeDescriptor, List<IExpressionAnnotation>>(edgeDescriptor, hintList);
   }
 }
 
 @new
-Pair<Set<ElementLabel>, Pair<Integer, Integer>> EdgeDetail() throws ParseException:
+Triple<Set<ElementLabel>, Pair<Integer, Integer>, Expression> EdgeDetail() throws ParseException:
 {
-  Set<ElementLabel> edgeLabels = new HashSet<ElementLabel>();
+  Set<String> edgeLabels = new HashSet<String>();
   Pair<Integer, Integer> repetitionQuantifier = null;
+  Expression filterExpr = null;
+  boolean isNegatedLabelSet = false;
   String labelName = null;
 }
 {
   (
-    // Note: we want to forbid LABEL_1|LABEL_2{...}.
-    LOOKAHEAD(2, <BAR>)
-    (
-      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
-      <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
-      ( <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); } )*
-    )
-    |
-    (
-      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
-      |
-      <LEFTPAREN>
-      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
-      ( <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); } )*
-      <RIGHTPAREN>
-    )
-    ( repetitionQuantifier = EdgeRepetitionQuantifier() )?
-    |
     ( repetitionQuantifier = EdgeRepetitionQuantifier() )
+    |
+    (
+      <COLON>
+      ( <CARET> { isNegatedLabelSet = true; } )?
+      edgeLabels = ParenthesizedLabelValueSet()
+      ( LOOKAHEAD(2)
+        ( <WHERE> filterExpr = Expression() )
+        |
+        ( repetitionQuantifier = EdgeRepetitionQuantifier() )
+      )?
+    )
+    |
+    ( <WHERE> filterExpr = Expression() )
   )
   {
-    return new Pair<Set<ElementLabel>, Pair<Integer, Integer>> (edgeLabels, repetitionQuantifier);
+    // Construct our label set.
+    Set<ElementLabel> labels = new HashSet<ElementLabel>();
+    for (String edgeLabelValue : edgeLabels) {
+      labels.add(new ElementLabel(edgeLabelValue, isNegatedLabelSet));
+    }
+    return new Triple<Set<ElementLabel>, Pair<Integer, Integer>, Expression> (labels, repetitionQuantifier, filterExpr);
+  }
+}
+
+@new
+Set<String> ParenthesizedLabelValueSet() throws ParseException:
+{
+  Set<String> labelSet = new HashSet<String>();
+  Set<String> innerLabelSet = null;
+  String labelName = null;
+}
+{
+  (
+    ( labelName = Identifier() { labelSet.add(labelName); } )
+    |
+    (
+      <LEFTPAREN>
+      innerLabelSet = ParenthesizedLabelValueSet() { labelSet.addAll(innerLabelSet); }
+      (
+        <BAR>
+        innerLabelSet = ParenthesizedLabelValueSet() { labelSet.addAll(innerLabelSet); }
+      )*
+      <RIGHTPAREN>
+    )
+  )
+  {
+    return labelSet;
   }
 }
 
@@ -751,12 +833,17 @@
   Integer hopCountMax = null;
 }
 {
-  <LEFTBRACE>
-  ( // Note: we forbid unbounded edge repetition.
-    ( <INTEGER_LITERAL> { hopCountMin = Integer.valueOf(token.image); } )?
-    <COMMA> <INTEGER_LITERAL> { hopCountMax = Integer.valueOf(token.image); }
+  (
+    <PLUS>
+    |
+    (
+      <LEFTBRACE>
+      ( <INTEGER_LITERAL> { hopCountMin = Integer.valueOf(token.image); } )?
+      <COMMA>
+      ( <INTEGER_LITERAL> { hopCountMax = Integer.valueOf(token.image); } )?
+      <RIGHTBRACE>
+    )
   )
-  <RIGHTBRACE>
   {
     return new Pair<Integer, Integer>(hopCountMin, hopCountMax);
   }
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.1.ddl.sqlpp
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.1.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.2.update.sqlpp
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.2.update.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.3.query.sqlpp
similarity index 95%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.3.query.sqlpp
index 8122ce1..9252f72 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.3.query.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Subquery expressing anti-join of patterns.
+// Subquery expressing anti-join of patterns from different graphs.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.4.query.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.4.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.4.query.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.5.query.sqlpp
new file mode 100644
index 0000000..f2d93cd
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/correlated-vertex-join/correlated-vertex-join.5.query.sqlpp
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+// Implicit correlated vertex anti-JOIN.
+DECLARE GRAPH  YelpGraph AS
+VERTEX         (:User)
+               PRIMARY KEY (user_id)
+               AS Yelp.Users,
+VERTEX         (:Review)
+               PRIMARY KEY (review_id)
+               AS Yelp.Reviews,
+EDGE           (:Review)-[:MADE_BY]->(:User)
+               SOURCE KEY (review_id)
+               DESTINATION KEY (user_id)
+               AS ( FROM    Yelp.Reviews R
+                    SELECT  R.review_id, R.user_id );
+
+FROM GRAPH  YelpGraph
+MATCH       (u:User)<-(r)
+WHERE       NOT EXISTS ( FROM   GRAPH YelpGraph
+                         MATCH  (u)
+                         WHERE  u.user_id = 1
+                         SELECT VALUE 1 )
+SELECT      u.user_id, r.review_id
+ORDER BY    u.user_id, r.review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
index 78851df..f9ea1cd 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
--- param max-warnings:string=2
+-- param max-warnings:string=3
 
 // There are two dangling vertices of different labels, and zero edges.
 FROM GRAPH  Yelp.YelpGraph
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
index e3f1e7c..49e851b 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
@@ -18,7 +18,7 @@
  */
 
 // There should be results where (u) = (v).
-SET  `graphix.match-evaluation` "homomorphism";
+SET  `graphix.semantics.pattern` "homomorphism";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp
index 0e2cbc9..8451d7d 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp
@@ -18,7 +18,7 @@
  */
 
 // There should be results where (u) = (w) (i.e. only edge adjacency should be preserved).
-SET  `graphix.match-evaluation` "homomorphism";
+SET  `graphix.semantics.pattern` "homomorphism";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
index 71e441c..7de7558 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
@@ -25,6 +25,6 @@
                    PRIMARY KEY (review_id)
                    AS Yelp.Reviews
 MATCH       (n)
-SELECT      LABEL(n) AS vertexLabel,
+SELECT      LABEL(n) AS elementLabel,
             VERTEX_DETAIL(n) AS vertexDetail
 ORDER BY    n;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
index 0283334..b468c26 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
@@ -43,7 +43,7 @@
             LABEL(e) AS edgeLabel,
             LABEL(n1) AS n1Label,
             LABEL(n2) AS n2Label,
-            LABEL(sourceVertex) AS sourceVertexLabel,
-            LABEL(destVertex) AS destVertexLabel,
+            LABEL(sourceVertex) AS sourceElementLabel,
+            LABEL(destVertex) AS destElementLabel,
             EDGE_DETAIL(e) AS edgeDetail
 ORDER BY    direction, edgeLabel, n1Label, n2Label, edgeDetail;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
index a427758..08319e2 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
@@ -27,7 +27,7 @@
                    AS ( FROM    Yelp.Friends F
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
-MATCH       (n1)-[e:{1,2}]->(n2)->(n3) AS p
+MATCH       (n1)-[e{1,2}]->(n2)->(n3) AS p
 SELECT      EDGES(p) AS pEdges,
             EDGES(e) AS eEdges,
             HOP_COUNT(p) AS pHopCount,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.13.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.13.query.sqlpp
new file mode 100644
index 0000000..a995d98
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.13.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+// Verify that a query that doesn't adhere to the graph schema returns an error.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            VERTEX  (:Vertex2)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex2)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v:Vertex1)<-[:EDGE_1]-(:Vertex2)
+SELECT      v;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
index 595463c..3538605 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
@@ -27,7 +27,7 @@
                    AS ( FROM    Yelp.Friends F
                         WHERE   F.friend_group = "A"
                         SELECT  F.user_id, F.friend )
-MATCH       (u)-[e1:{1,2}]-(v) AS p
+MATCH       (u)-[e1{1,2}]-(v) AS p
 SELECT      LEN(p.Edges) AS pathLength,
             u.user_id AS u_user_id,
             v.user_id AS v_user_id
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.1.ddl.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.1.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.2.update.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.2.update.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.3.query.sqlpp
new file mode 100644
index 0000000..b11858b
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/same-edge-label/same-edge-label.3.query.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+// Query with a schema edge of the same label.
+DECLARE GRAPH  YelpGraph AS
+VERTEX         (:User)
+               PRIMARY KEY (user_id)
+               AS Yelp.Users,
+VERTEX         (:Review)
+               PRIMARY KEY (review_id)
+               AS Yelp.Reviews,
+EDGE           (:Review)-[:RELATED_TO]->(:User)
+               SOURCE KEY (review_id)
+               DESTINATION KEY (user_id)
+               AS ( FROM    Yelp.Reviews R
+                    SELECT  R.review_id, R.user_id ),
+EDGE           (:User)-[:RELATED_TO]->(:User)
+               SOURCE KEY (user_id)
+               DESTINATION KEY (friend)
+               AS ( FROM    Yelp.Friends F
+                    SELECT  F.user_id, F.friend );
+
+FROM     GRAPH YelpGraph
+MATCH    (u)-[:RELATED_TO]->(v)
+SELECT   v.user_id AS v_user_id,
+         CASE
+         WHEN LABEL(u) = "User"
+         THEN { "user_id": u.user_id }
+         WHEN LABEL(u) = "Review"
+         THEN { "review_id": u.review_id }
+         END AS u_record
+ORDER BY u, v;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.1.ddl.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.1.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.1.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.2.update.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.2.update.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.2.update.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.3.query.sqlpp
similarity index 96%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.3.query.sqlpp
index 700fe8f..f1e7af5 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.3.query.sqlpp
@@ -20,7 +20,6 @@
 -- param max-warnings:string=1
 
 // We should be able to resolve both e and n, using the edge direction.
-SET         `graphix.resolver` "inference-based";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.4.query.sqlpp
similarity index 97%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.4.query.sqlpp
index 5533ede..1b4282a 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.4.query.sqlpp
@@ -20,7 +20,6 @@
 -- param max-warnings:string=1
 
 // We should be able to resolve all elements (e, n, f, m), using the edge direction of the last edge in our pattern.
-SET         `graphix.resolver` "inference-based";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.5.query.sqlpp
similarity index 97%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.5.query.sqlpp
index df7e676..9202011 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.5.query.sqlpp
@@ -20,7 +20,6 @@
 -- param max-warnings:string=1
 
 // We should be able to resolve all elements (e, n, f, last direction), using the edge direction of the first pattern.
-SET         `graphix.resolver` "inference-based";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.6.query.sqlpp
similarity index 94%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.6.query.sqlpp
index 30de634..fa256a5 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.6.query.sqlpp
@@ -20,7 +20,6 @@
 -- param max-warnings:string=1
 
 // We should be able to resolve all elements (e, n, f, last direction), using the edge direction of the first pattern.
-SET         `graphix.resolver` "inference-based";
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
@@ -39,7 +38,7 @@
                    DESTINATION KEY (user_id)
                    AS ( FROM    Yelp.Reviews R
                         SELECT  R.review_id, R.user_id )
-MATCH       (u:User)-[e:{2,2}]->(n) // (u:User)-[]->()-[]->(n)
+MATCH       (u:User)-[e{2,2}]->(n) // (u:User)-[]->()-[]->(n)
 UNNEST      PATH_EDGES(e) AS ee
 SELECT      DISTINCT LABEL(ee) AS e_label,
                      LABEL(n) AS n_label,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.7.query.sqlpp
similarity index 89%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.7.query.sqlpp
index 700fe8f..780f1a2 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.7.query.sqlpp
@@ -19,8 +19,7 @@
 
 -- param max-warnings:string=1
 
-// We should be able to resolve both e and n, using the edge direction.
-SET         `graphix.resolver` "inference-based";
+// We should be able to resolve both u and n, using the negated edge label.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
@@ -39,6 +38,6 @@
                    DESTINATION KEY (user_id)
                    AS ( FROM    Yelp.Reviews R
                         SELECT  R.review_id, R.user_id )
-MATCH       (u:User)-[e]->(n)
-SELECT      DISTINCT LABEL(e) AS e_label,
+MATCH       (u)-[e:^MADE_BY]-(n)
+SELECT      DISTINCT LABEL(u) AS u_label,
                      LABEL(n) AS n_label;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.8.query.sqlpp
new file mode 100644
index 0000000..34b03d6
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/schema-resolution/schema-resolution.8.query.sqlpp
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+-- param max-warnings:string=1
+
+// We should be able to resolve both subquery vertex JOINs in both directions.
+DECLARE GRAPH  YelpGraph AS
+VERTEX         (:User)
+               PRIMARY KEY (user_id)
+               AS ( FROM    Yelp.Users U
+                    SELECT  U.user_id ),
+VERTEX         (:Review)
+               PRIMARY KEY (review_id)
+               AS ( FROM    Yelp.Reviews R
+                    SELECT  R.review_id ),
+EDGE           (:User)-[:FRIENDS_WITH]->(:User)
+               SOURCE KEY (user_id)
+               DESTINATION KEY (friend)
+               AS ( FROM    Yelp.Friends F
+                    SELECT  F.user_id, F.friend ),
+EDGE           (:Review)-[:MADE_BY]->(:User)
+               SOURCE KEY (review_id)
+               DESTINATION KEY (user_id)
+               AS ( FROM    Yelp.Reviews R
+                    SELECT  R.review_id, R.user_id );
+
+FROM   GRAPH YelpGraph
+MATCH  (u)->(r)
+WHERE  EXISTS ( FROM   GRAPH YelpGraph
+                MATCH  (u:User)
+                WHERE  u.user_id = 1
+                SELECT VALUE 1 )
+SELECT DISTINCT LABEL(u) AS u_label,
+                LABEL(r) AS r_label
+
+UNION ALL
+
+FROM   GRAPH YelpGraph
+MATCH  (u:User)->(r)
+WHERE  EXISTS ( FROM   GRAPH YelpGraph
+                MATCH  (u)
+                WHERE  u.user_id = 1
+                SELECT VALUE 1 )
+SELECT DISTINCT LABEL(u) AS u_label,
+                LABEL(r) AS r_label;
+
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.1.ddl.sqlpp
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.1.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.2.update.sqlpp
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.2.update.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.7.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.3.query.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.7.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.3.query.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.4.query.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.8.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-validation/scope-validation.4.query.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
similarity index 63%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
index 8122ce1..ecec02d 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-// Subquery expressing anti-join of patterns.
+// We have a single edge with a filter expression on the User vertex.
+LET         startingUserID = 1
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
@@ -26,17 +27,11 @@
                    AS Yelp.Reviews,
             EDGE   (:Review)-[:MADE_BY]->(:User)
                    SOURCE KEY (review_id)
-                   DESTINATION KEY (user_id)
+                   DESTINATION KEY (review_user_id)
                    AS ( FROM    Yelp.Reviews R
-                        SELECT  R.review_id, R.user_id )
-MATCH       (u:User)<-(r)
-WHERE       NOT EXISTS ( FROM GRAPH   VERTEX (:User)
-                                      PRIMARY KEY (user_id)
-                                      AS ( FROM    Yelp.Users U
-                                           WHERE   U.user_id = 1
-                                           SELECT  VALUE U )
-                          MATCH       (innerU:User)
-                          WHERE       innerU = u
-                          SELECT      VALUE 1 )
-SELECT      u.user_id
-ORDER BY    u.user_id;
\ No newline at end of file
+                        SELECT  R.review_user_id, R.review_id )
+MATCH       (u:User WHERE u.user_id = startingUserID)<-[:MADE_BY]-(r:Review)
+SELECT      u.user_id,
+            r.review_id
+ORDER BY    u.user_id,
+            r.review_id;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.5.update.sqlpp
similarity index 69%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.5.update.sqlpp
index 4509792..fdddf8d 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.5.update.sqlpp
@@ -16,11 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.lower.transform;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+USE               Yelp;
 
-@FunctionalInterface
-public interface ISequenceTransformer {
-    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
-}
+DELETE FROM       Users;
+DELETE FROM       Friends;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 3, "friend": 4 },
+  { "user_id": 1, "friend": 3 }
+];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.6.query.sqlpp
similarity index 97%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.6.query.sqlpp
index c0c8d48..91bbdfa 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.6.query.sqlpp
@@ -26,7 +26,7 @@
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
                         SELECT  VALUE F )
-MATCH       (u1)-[:{1,3}]->(u2) AS p
+MATCH       (u1)-[{1,3}]->(u2) AS p
 GROUP BY    u1, u2
 GROUP AS    g
 LET         shortestPath = ( FROM      g AS gi
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.7.query.sqlpp
similarity index 94%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.7.query.sqlpp
index 62e6a83..99c8c02 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.7.query.sqlpp
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+// Subquery + GROUP BY expressing (bounded) shortest-path.
 // Subquery + GROUP BY expressing (bounded) shortest-path, using aliases.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
@@ -26,7 +27,7 @@
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
                         SELECT  VALUE F )
-MATCH       (u1)-[:{1,3}]->(u2) AS p
+MATCH       (u1)-[{1,3}]->(u2) AS p
 LET         pathHopCount = PATH_HOP_COUNT(p),
             pathVertices = PATH_VERTICES(p),
             myUser1 = u1,
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.3.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.3.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.3.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.4.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.4.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.5.adm
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm
copy to asterix-graphix/src/test/resources/runtimets/results/graphix/correlated-vertex-join/correlated-vertex-join.5.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
index c514419..47003ca 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
@@ -1,8 +1,8 @@
-{ "vertexLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "A" ] } }
-{ "vertexLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "B" ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 1 ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 2 ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 3 ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 4 ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 5 ] } }
-{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 6 ] } }
\ No newline at end of file
+{ "elementLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "A" ] } }
+{ "elementLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "B" ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 1 ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 2 ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 3 ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 4 ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 5 ] } }
+{ "elementLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 6 ] } }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
index c81b251..bfe59dc 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
@@ -1,14 +1,14 @@
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "Review", "n2Label": "User", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
-{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "Review", "n2Label": "User", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "User", "n2Label": "Review", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
-{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "Review", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
\ No newline at end of file
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "Review", "n2Label": "User", "sourceElementLabel": "Review", "destElementLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "Review", "n2Label": "User", "sourceElementLabel": "Review", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceElementLabel": "User", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "User", "n2Label": "Review", "sourceElementLabel": "Review", "destElementLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "Review", "sourceElementLabel": "Review", "destElementLabel": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/same-edge-label/same-edge-label.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/same-edge-label/same-edge-label.3.adm
new file mode 100644
index 0000000..42acc6b
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/same-edge-label/same-edge-label.3.adm
@@ -0,0 +1,10 @@
+{ "u_record": { "review_id": "A" }, "v_user_id": 1 }
+{ "u_record": { "review_id": "B" }, "v_user_id": 1 }
+{ "u_record": { "review_id": "C" }, "v_user_id": 1 }
+{ "u_record": { "review_id": "D" }, "v_user_id": 2 }
+{ "u_record": { "review_id": "E" }, "v_user_id": 3 }
+{ "u_record": { "review_id": "F" }, "v_user_id": 4 }
+{ "u_record": { "user_id": 1 }, "v_user_id": 2 }
+{ "u_record": { "user_id": 1 }, "v_user_id": 3 }
+{ "u_record": { "user_id": 2 }, "v_user_id": 3 }
+{ "u_record": { "user_id": 3 }, "v_user_id": 4 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.3.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.3.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.3.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.4.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.4.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.4.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.5.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.5.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.5.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.6.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.6.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.7.adm
new file mode 100644
index 0000000..1588f9f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.7.adm
@@ -0,0 +1 @@
+{ "u_label": "User", "n_label": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.8.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.8.adm
new file mode 100644
index 0000000..2677371
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/schema-resolution/schema-resolution.8.adm
@@ -0,0 +1,2 @@
+{ "u_label": "User", "r_label": "User" }
+{ "u_label": "User", "r_label": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm
deleted file mode 100644
index 9b24dd0..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm
+++ /dev/null
@@ -1,6 +0,0 @@
-{ "user_id": 1, "review_id": "A" }
-{ "user_id": 1, "review_id": "B" }
-{ "user_id": 1, "review_id": "C" }
-{ "user_id": 2, "review_id": "D" }
-{ "user_id": 3, "review_id": "E" }
-{ "user_id": 4, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-validation/scope-validation.3.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/scope-validation/scope-validation.3.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-validation/scope-validation.4.adm
similarity index 100%
copy from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm
copy to asterix-graphix/src/test/resources/runtimets/results/graphix/scope-validation/scope-validation.4.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
new file mode 100644
index 0000000..2a5d87f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
@@ -0,0 +1,2 @@
+{ "user_id": 1, "review_id": "A" }
+{ "user_id": 1, "review_id": "B" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.6.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.6.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.7.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.7.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/testsuite.xml b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
index a5ea9fc..50abf0c 100644
--- a/asterix-graphix/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
@@ -20,6 +20,13 @@
             ResultOffsetPath="results"
             QueryOffsetPath="queries"
             QueryFileExtension=".sqlpp">
+  <test-group name="correlated-vertex-join">
+    <test-case FilePath="graphix">
+      <compilation-unit name="correlated-vertex-join">
+        <output-dir compare="Text">correlated-vertex-join</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
   <test-group name="create-drop-error">
     <test-case FilePath="graphix">
       <compilation-unit name="create-drop-error">
@@ -42,10 +49,12 @@
     </test-case>
   </test-group>
   <test-group name="dangling-vertices">
-    <test-case FilePath="graphix" check-warnings="true">
+    <!--<test-case FilePath="graphix" check-warnings="true">-->
+    <test-case FilePath="graphix" check-warnings="false">
       <compilation-unit name="dangling-vertices">
         <output-dir compare="Text">dangling-vertices</output-dir>
-        <expected-warn>Potential disconnected pattern encountered! A CROSS-JOIN has been introduced.</expected-warn>
+        <!--<expected-warn>Disconnected pattern encountered! A CROSS-JOIN may been introduced.</expected-warn>-->
+        <!--<expected-warn>Encountered a cross product join</expected-warn>-->
       </compilation-unit>
     </test-case>
   </test-group>
@@ -70,13 +79,6 @@
       </compilation-unit>
     </test-case>
   </test-group>
-  <test-group name="inference-resolution">
-    <test-case FilePath="graphix" check-warnings="true">
-      <compilation-unit name="inference-resolution">
-        <output-dir compare="Text">inference-resolution</output-dir>
-      </compilation-unit>
-    </test-case>
-  </test-group>
   <test-group name="left-match">
     <test-case FilePath="graphix">
       <compilation-unit name="left-match">
@@ -99,6 +101,7 @@
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>
+        <expected-error>Encountered graph element that does not conform the queried graph schema!</expected-error>
       </compilation-unit>
     </test-case>
   </test-group>
@@ -109,10 +112,17 @@
       </compilation-unit>
     </test-case>
   </test-group>
-  <test-group name="scope-checking">
+  <test-group name="schema-resolution">
+    <test-case FilePath="graphix" check-warnings="true">
+      <compilation-unit name="schema-resolution">
+        <output-dir compare="Text">schema-resolution</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="scope-validation">
     <test-case FilePath="graphix">
-      <compilation-unit name="scope-checking">
-        <output-dir compare="Text">scope-checking</output-dir>
+      <compilation-unit name="scope-validation">
+        <output-dir compare="Text">scope-validation</output-dir>
       </compilation-unit>
     </test-case>
   </test-group>