You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@asterixdb.apache.org by gg...@apache.org on 2022/09/25 22:17:06 UTC

[asterixdb-graph] branch master updated: [NO-ISSUE][GRAPHIX] Large Graphix update.

This is an automated email from the ASF dual-hosted git repository.

ggalvizo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb-graph.git


The following commit(s) were added to refs/heads/master by this push:
     new acd4b5b  [NO-ISSUE][GRAPHIX] Large Graphix update.
acd4b5b is described below

commit acd4b5b4b9d02d1eb92ac566d94dda44ce67e1e9
Author: ggalvizo <gg...@uci.edu>
AuthorDate: Sun Sep 25 14:29:42 2022 -0700

    [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 <je...@fulliautomatix.ics.uci.edu>
    Reviewed-by: Glenn Galvizo <gg...@uci.edu>
---
 asterix-graphix/pom.xml                            |   5 +
 .../compiler/option/ElementEvaluationOption.java}  |  22 +-
 .../compiler/option/IGraphixCompilerOption.java}   |  13 +-
 .../compiler/option/SchemaDecorateEdgeOption.java} |  23 +-
 .../option/SchemaDecorateVertexOption.java}        |  23 +-
 .../option/SemanticsNavigationOption.java}         |  23 +-
 .../compiler/option/SemanticsPatternOption.java}   |  24 +-
 .../provider/GraphixCompilationProvider.java       |  22 +-
 .../GraphixExpressionToPlanTranslator.java         |  79 +++
 .../app/translator/GraphixQueryTranslator.java     |  58 +-
 .../translator/GraphixQueryTranslatorFactory.java  |  10 +-
 .../graphix/common/metadata/EdgeIdentifier.java    |  90 +++
 .../graphix/common/metadata/GraphIdentifier.java   |   8 +-
 .../metadata/IElementIdentifier.java}              |   8 +-
 ...lementIdentifier.java => VertexIdentifier.java} |  46 +-
 .../extension/GraphixQueryTranslatorExtension.java |   3 +-
 .../function/GraphixFunctionIdentifiers.java       |  18 +-
 .../graphix/function/GraphixFunctionMap.java       |   6 +-
 .../function/prepare/AbstractElementPrepare.java   |   2 +-
 .../function/prepare/EdgeDestVertexPrepare.java    |   4 +-
 .../function/prepare/EdgeDetailPrepare.java        |   8 +-
 .../function/prepare/EdgeDirectionPrepare.java     |   2 +-
 .../function/prepare/EdgeSourceVertexPrepare.java  |   4 +-
 .../graphix/function/prepare/IFunctionPrepare.java |   2 +-
 .../function/prepare/VertexDetailPrepare.java      |   6 +-
 .../graphix/function/rewrite/IFunctionRewrite.java |   2 +-
 .../graphix/function/rewrite/PathEdgesRewrite.java |   6 +-
 .../function/rewrite/PathHopCountRewrite.java      |   6 +-
 .../function/rewrite/PathVerticesRewrite.java      |   6 +-
 .../function/rewrite/SchemaAccessRewrite.java      |   2 +-
 .../ElementEvaluationAnnotation.java}              |  26 +-
 .../LoweringExemptAnnotation.java}                 |  16 +-
 .../SubqueryVertexJoinAnnotation.java}             |  28 +-
 .../asterix/graphix/lang/clause/CorrLetClause.java |  64 --
 .../graphix/lang/clause/CorrWhereClause.java       |  57 --
 .../graphix/lang/clause/FromGraphClause.java       |  95 ++-
 .../graphix/lang/clause/GraphSelectBlock.java      | 108 ----
 .../graphix/lang/clause/LowerListClause.java       |  74 +++
 .../graphix/lang/clause/LowerSwitchClause.java     | 238 +++++++
 .../asterix/graphix/lang/clause/MatchClause.java   |  21 +-
 .../extension/IGraphixVisitorExtension.java}       |  18 +-
 .../clause/extension/LowerListClauseExtension.java | 339 ++++++++++
 .../extension/LowerSwitchClauseExtension.java      | 252 ++++++++
 .../graphix/lang/expression/EdgePatternExpr.java   |  62 +-
 .../graphix/lang/expression/GraphConstructor.java  |  31 +-
 .../graphix/lang/expression/PathPatternExpr.java   |  22 +-
 .../graphix/lang/expression/VertexPatternExpr.java |  33 +-
 .../GraphixParserHint.java}                        |  32 +-
 .../graphix/lang/rewrite/GraphixQueryRewriter.java | 343 ++++++++++
 .../GraphixRewriterFactory.java                    |   2 +-
 .../lang/rewrite/GraphixRewritingContext.java      | 129 ++++
 .../canonical/CanonicalElementBranchConsumer.java  |  61 ++
 .../CanonicalElementExpansionConsumer.java         | 331 ++++++++++
 .../CanonicalElementGeneratorFactory.java          | 189 ++++++
 .../canonical/ICanonicalElementConsumer.java}      |   9 +-
 .../lang/rewrite/common/BranchLookupTable.java     |  49 ++
 .../common/ElementLookupTable.java                 |  30 +-
 .../lang/rewrite/lower/AliasLookupTable.java       |  62 ++
 .../rewrite/lower/EnvironmentActionFactory.java    | 692 +++++++++++++++++++++
 .../lang/rewrite/lower/LoweringEnvironment.java    | 366 +++++++++++
 .../lower/action/AbstractInlineAction.java         | 104 ++--
 .../lower/action/IEnvironmentAction.java           |   4 +-
 .../lower/action/ILowerListTransformer.java}       |  10 +-
 .../rewrite/lower/action/MatchSemanticAction.java  | 332 ++++++++++
 .../lower/action/PathPatternAction.java            |  31 +-
 .../rewrite/lower/struct/ClauseCollection.java     | 232 +++++++
 .../lang/rewrite/lower/struct/CollectionTable.java | 174 ++++++
 .../lang/rewrite/lower/struct/StateContainer.java  |  66 ++
 .../print/GraphixASTPrintVisitor.java              |  39 +-
 .../print/GraphixASTPrintVisitorFactory.java       |   2 +-
 .../print/SqlppASTPrintQueryVisitor.java           | 110 +++-
 .../rewrite/resolve/ExhaustiveSearchResolver.java  | 502 +++++++++++++++
 .../resolve/IInternalVertexSupplier.java}          |   7 +-
 .../resolve/IPatternGroupResolver.java}            |  10 +-
 .../lang/rewrite/resolve/SchemaKnowledgeTable.java | 227 +++++++
 .../lang/rewrite/util/CanonicalElementUtil.java    | 221 +++++++
 .../util/LowerRewritingUtil.java                   |   7 +-
 .../rewrite/visitor/AmbiguousElementVisitor.java   | 166 +++++
 .../visitor/ElementBodyAnalysisVisitor.java        |  31 +-
 .../visitor/ElementLookupTableVisitor.java         |  40 +-
 .../visitor/ElementSubstitutionVisitor.java        | 101 +++
 .../visitor/FunctionResolutionVisitor.java         |   8 +-
 .../visitor/GraphixDeepCopyVisitor.java            |  92 ++-
 .../visitor/GraphixFunctionCallVisitor.java        |   4 +-
 .../rewrite/visitor/GraphixLoweringVisitor.java    | 530 ++++++++++++++++
 .../rewrite/visitor/PatternGraphGroupVisitor.java  |  87 +++
 .../visitor/PopulateUnknownsVisitor.java           | 102 +--
 .../visitor/PostCanonicalExpansionVisitor.java}    | 100 +--
 .../visitor/PostRewriteCheckVisitor.java           |  24 +-
 .../visitor/PreRewriteCheckVisitor.java            | 111 +++-
 .../visitor/QueryCanonicalizationVisitor.java      | 209 +++++++
 .../rewrite/visitor/SchemaEnrichmentVisitor.java   | 177 ++++++
 .../rewrite/visitor/SubqueryVertexJoinVisitor.java | 147 +++++
 .../rewrite/visitor/VariableRemapCloneVisitor.java | 148 +++++
 .../visitor/VariableScopingCheckVisitor.java}      |  90 ++-
 .../lang/rewrites/GraphixQueryRewriter.java        | 287 ---------
 .../lang/rewrites/GraphixRewritingContext.java     |  62 --
 .../rewrites/canonical/DanglingVertexExpander.java |  93 ---
 .../rewrites/canonical/EdgeSubPathExpander.java    | 304 ---------
 .../lang/rewrites/common/EdgeDependencyGraph.java  | 131 ----
 .../rewrites/lower/EnvironmentActionFactory.java   | 537 ----------------
 .../rewrites/lower/LoweringAliasLookupTable.java   |  53 --
 .../lang/rewrites/lower/LoweringEnvironment.java   | 183 ------
 .../rewrites/lower/action/IsomorphismAction.java   | 306 ---------
 .../lower/transform/CorrelatedClauseSequence.java  |  89 ---
 .../rewrites/resolve/InferenceBasedResolver.java   | 224 -------
 .../lang/rewrites/resolve/QueryKnowledgeTable.java |  46 --
 .../rewrites/resolve/SchemaKnowledgeTable.java     | 120 ----
 .../visitor/CanonicalExpansionVisitor.java         | 279 ---------
 .../rewrites/visitor/GraphixLoweringVisitor.java   | 315 ----------
 .../rewrites/visitor/GroupByAggSugarVisitor.java   |  58 --
 .../rewrites/visitor/LabelConsistencyVisitor.java  |  49 --
 .../visitor/PostRewriteVariableVisitor.java        |  60 --
 .../rewrites/visitor/SchemaEnrichmentVisitor.java  | 121 ----
 .../rewrites/visitor/StructureAnalysisVisitor.java | 231 -------
 .../visitor/StructureResolutionVisitor.java        | 167 -----
 .../visitor/VariableSubstitutionVisitor.java       |  55 --
 .../lang/statement/CreateGraphStatement.java       |  12 +-
 .../lang/statement/DeclareGraphStatement.java      |   4 +-
 .../graphix/lang/statement/GraphDropStatement.java |  10 +-
 .../lang/statement/GraphElementDeclaration.java    |  10 +-
 .../graphix/lang/struct/EdgeDescriptor.java        |  69 +-
 .../asterix/graphix/lang/struct/ElementLabel.java  |  33 +-
 .../asterix/graphix/lang/struct/PatternGroup.java  |  86 +++
 .../lang/util/GraphStatementHandlingUtil.java      |  11 +-
 .../base}/AbstractGraphixQueryVisitor.java         |  54 +-
 .../base}/IGraphixLangVisitor.java                 |   5 +-
 .../entity/dependency/FunctionRequirements.java    |   2 +-
 .../entity/dependency/ViewRequirements.java        |   2 +-
 .../graphix/metadata/entity/schema/Edge.java       |  34 +-
 .../graphix/metadata/entity/schema/IElement.java   |   8 +-
 .../graphix/metadata/entity/schema/Schema.java     |  63 +-
 .../graphix/metadata/entity/schema/Vertex.java     |  18 +-
 .../GraphTupleTranslator.java                      |  13 +-
 .../evaluator/AppendInternalPathDescriptor.java    | 142 +++++
 .../evaluator/CreateInternalPathDescriptor.java    |  93 +++
 .../evaluator/IsDistinctEdgeDescriptor.java        |  99 +++
 .../evaluator/IsDistinctEverythingDescriptor.java  | 109 ++++
 .../evaluator/IsDistinctVertexDescriptor.java      |  99 +++
 .../common/AbstractElementCompareEvaluator.java    |  85 +++
 .../function/GraphixFunctionInfoCollection.java    |  79 +++
 .../function/GraphixFunctionRegistrant.java        |  59 ++
 .../runtime/pointable/InternalPathPointable.java   |  80 +++
 .../pointable/SinglyLinkedListPointable.java       |  73 +++
 .../graphix/type/MaterializePathTypeComputer.java  |  47 ++
 ...apache.asterix.om.functions.IFunctionRegistrant |  20 +
 asterix-graphix/src/main/resources/cc.conf         |   8 +-
 .../src/main/resources/lang-extension/lang.txt     | 203 ++++--
 .../correlated-vertex-join.1.ddl.sqlpp}            |   0
 .../correlated-vertex-join.2.update.sqlpp}         |   0
 .../correlated-vertex-join.3.query.sqlpp}          |   2 +-
 .../correlated-vertex-join.4.query.sqlpp}          |   0
 .../correlated-vertex-join.5.query.sqlpp}          |  34 +-
 .../dangling-vertices.5.query.sqlpp                |   2 +-
 .../graph-isomorphism.6.query.sqlpp                |   2 +-
 .../graph-isomorphism.7.query.sqlpp                |   2 +-
 .../graphix-functions.3.query.sqlpp                |   2 +-
 .../graphix-functions.4.query.sqlpp                |   4 +-
 .../graphix-functions.5.query.sqlpp                |   2 +-
 .../on-query-error/on-query-error.13.query.sqlpp}  |  26 +-
 .../path-variable/path-variable.6.query.sqlpp      |   2 +-
 .../same-edge-label.1.ddl.sqlpp}                   |   0
 .../same-edge-label.2.update.sqlpp}                |   0
 .../same-edge-label/same-edge-label.3.query.sqlpp  |  48 ++
 .../schema-resolution.1.ddl.sqlpp}                 |   0
 .../schema-resolution.2.update.sqlpp}              |   0
 .../schema-resolution.3.query.sqlpp}               |   1 -
 .../schema-resolution.4.query.sqlpp}               |   1 -
 .../schema-resolution.5.query.sqlpp}               |   1 -
 .../schema-resolution.6.query.sqlpp}               |   3 +-
 .../schema-resolution.7.query.sqlpp}               |   7 +-
 .../schema-resolution.8.query.sqlpp                |  62 ++
 .../scope-validation.1.ddl.sqlpp}                  |   0
 .../scope-validation.2.update.sqlpp}               |   0
 .../scope-validation.3.query.sqlpp}                |   0
 .../scope-validation.4.query.sqlpp}                |   0
 .../simple-1-edge.6.query.sqlpp}                   |  18 +-
 .../variable-sub-path.5.update.sqlpp}              |  12 +-
 .../variable-sub-path.6.query.sqlpp}               |   2 +-
 .../variable-sub-path.7.query.sqlpp}               |   3 +-
 .../correlated-vertex-join.3.adm}                  |   0
 .../correlated-vertex-join.4.adm}                  |   0
 .../correlated-vertex-join.5.adm}                  |   0
 .../graphix-functions/graphix-functions.3.adm      |  16 +-
 .../graphix-functions/graphix-functions.4.adm      |  28 +-
 .../graphix/same-edge-label/same-edge-label.3.adm  |  10 +
 .../schema-resolution.3.adm}                       |   0
 .../schema-resolution.4.adm}                       |   0
 .../schema-resolution.5.adm}                       |   0
 .../schema-resolution.6.adm}                       |   0
 .../schema-resolution/schema-resolution.7.adm      |   1 +
 .../schema-resolution/schema-resolution.8.adm      |   2 +
 .../scope-validation.3.adm}                        |   0
 .../scope-validation.4.adm}                        |   0
 .../graphix/simple-1-edge/simple-1-edge.6.adm      |   2 +
 .../variable-sub-path.6.adm}                       |   0
 .../variable-sub-path.7.adm}                       |   0
 .../src/test/resources/runtimets/testsuite.xml     |  34 +-
 198 files changed, 9289 insertions(+), 4995 deletions(-)

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
@@ -189,6 +189,11 @@
   </build>
 
   <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>
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/algebra/compiler/option/ElementEvaluationOption.java
similarity index 58%
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/algebra/compiler/option/ElementEvaluationOption.java
index ff00077..a44fc74 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/algebra/compiler/option/ElementEvaluationOption.java
@@ -16,16 +16,24 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.algebra.compiler.option;
 
-import java.io.PrintWriter;
+import java.util.Locale;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+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("_", "-");
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
     @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    public String getDisplayName() {
+        return String.format("%s ( %s )", OPTION_KEY_NAME, getOptionValue());
     }
 }
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
similarity index 75%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
index 78851df..5cb0027 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/IGraphixCompilerOption.java
@@ -16,13 +16,10 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.algebra.compiler.option;
 
--- param max-warnings:string=2
+public interface IGraphixCompilerOption {
+    String getOptionValue();
 
-// There are two dangling vertices of different labels, and zero edges.
-FROM GRAPH  Yelp.YelpGraph
-MATCH       (u:User), (r:Review)
-SELECT      u.user_id,
-            r.review_id
-ORDER BY    u.user_id,
-            r.review_id;
+    String getDisplayName();
+}
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/algebra/compiler/option/SchemaDecorateEdgeOption.java
similarity index 58%
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/algebra/compiler/option/SchemaDecorateEdgeOption.java
index ff00077..e67c6cb 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/algebra/compiler/option/SchemaDecorateEdgeOption.java
@@ -16,16 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.algebra.compiler.option;
 
-import java.io.PrintWriter;
+import java.util.Locale;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+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("_", "-");
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
     @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    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/print/GraphixASTPrintVisitorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SchemaDecorateVertexOption.java
similarity index 58%
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/algebra/compiler/option/SchemaDecorateVertexOption.java
index ff00077..7a0cb73 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/algebra/compiler/option/SchemaDecorateVertexOption.java
@@ -16,16 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.algebra.compiler.option;
 
-import java.io.PrintWriter;
+import java.util.Locale;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+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("_", "-");
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
     @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    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/print/GraphixASTPrintVisitorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsNavigationOption.java
similarity index 56%
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/algebra/compiler/option/SemanticsNavigationOption.java
index ff00077..7715387 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/algebra/compiler/option/SemanticsNavigationOption.java
@@ -16,16 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.print;
+package org.apache.asterix.graphix.algebra.compiler.option;
 
-import java.io.PrintWriter;
+import java.util.Locale;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+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("_", "-");
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
     @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    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/print/GraphixASTPrintVisitorFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/option/SemanticsPatternOption.java
similarity index 56%
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/algebra/compiler/option/SemanticsPatternOption.java
index ff00077..559686f 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/algebra/compiler/option/SemanticsPatternOption.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.algebra.compiler.option;
 
-import java.io.PrintWriter;
+import java.util.Locale;
 
-import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
-import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+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("_", "-");
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
     @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    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 java.util.Set;
 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 @@ public class GraphixCompilationProvider extends SqlppCompilationProvider {
     @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 @@ package org.apache.asterix.graphix.app.translator;
 
 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.functions.FunctionSignature;
 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.MetadataTransactionContext;
 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 @@ public class GraphixQueryTranslator extends QueryTranslator {
             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 @@ public class GraphixQueryTranslator extends QueryTranslator {
 
     /**
      * 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 @@ public class GraphixQueryTranslator extends QueryTranslator {
 
     /**
      * 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 @@ public class GraphixQueryTranslator extends QueryTranslator {
 
     /**
      * 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 @@ public class GraphixQueryTranslator extends QueryTranslator {
 
     /**
      * 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.common.dataflow.ICcApplicationContext;
 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/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 @@ import org.apache.asterix.common.metadata.DataverseName;
 
 /**
  * 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 @@ public class GraphIdentifier implements Serializable {
         }
         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 76%
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/GraphElementIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/VertexIdentifier.java
similarity index 52%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/VertexIdentifier.java
index f440c2f..12fdb9d 100644
--- 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/VertexIdentifier.java
@@ -18,44 +18,39 @@
  */
 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.
+ * 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 GraphElementIdentifier implements Serializable {
+public class VertexIdentifier implements IElementIdentifier {
     private static final long serialVersionUID = 1L;
     private final GraphIdentifier graphIdentifier;
-    private final Kind elementKind;
-    private final ElementLabel elementLabel;
+    private final ElementLabel vertexLabel;
 
-    public GraphElementIdentifier(GraphIdentifier graphIdentifier, Kind elementKind, ElementLabel elementLabel) {
+    public VertexIdentifier(GraphIdentifier graphIdentifier, ElementLabel vertexLabel) {
         this.graphIdentifier = graphIdentifier;
-        this.elementKind = elementKind;
-        this.elementLabel = elementLabel;
+        this.vertexLabel = vertexLabel;
     }
 
+    @Override
     public GraphIdentifier getGraphIdentifier() {
         return graphIdentifier;
     }
 
-    public Kind getElementKind() {
-        return elementKind;
-    }
-
-    public ElementLabel getElementLabel() {
-        return elementLabel;
+    public ElementLabel getVertexLabel() {
+        return vertexLabel;
     }
 
     @Override
     public String toString() {
-        return graphIdentifier + "#" + elementLabel + " ( " + elementKind + " )";
+        return String.format("%s (:%s)", graphIdentifier, vertexLabel);
     }
 
     @Override
@@ -63,21 +58,16 @@ public class GraphElementIdentifier implements Serializable {
         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);
+        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, elementKind, elementLabel);
-    }
-
-    public enum Kind {
-        VERTEX,
-        EDGE
+        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 class GraphixQueryTranslatorExtension implements IStatementExecutorExtens
     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 @@ public class GraphixQueryTranslatorExtension implements IStatementExecutorExtens
 
     @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 class GraphixFunctionIdentifiers {
     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.PathEdgesRewrite;
 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.exceptions.CompilationException;
 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.base.Expression;
 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 @@ public class EdgeDestVertexPrepare extends AbstractElementPrepare {
         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.List;
 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 @@ public class EdgeDetailPrepare extends AbstractElementPrepare {
             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 @@ public class EdgeDetailPrepare extends AbstractElementPrepare {
         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 @@ public class EdgeDirectionPrepare extends AbstractElementPrepare {
         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 @@ public class EdgeSourceVertexPrepare extends AbstractElementPrepare {
         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 @@ package org.apache.asterix.graphix.function.prepare;
 
 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.List;
 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 @@ public class VertexDetailPrepare extends AbstractElementPrepare {
         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 @@ package org.apache.asterix.graphix.function.rewrite;
 
 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 @@ package org.apache.asterix.graphix.function.rewrite;
 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 @@ public class PathEdgesRewrite implements IFunctionRewrite {
             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.CompilationException;
 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 @@ public class PathHopCountRewrite implements IFunctionRewrite {
 
         // 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 @@ package org.apache.asterix.graphix.function.rewrite;
 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 @@ public class PathVerticesRewrite implements IFunctionRewrite {
             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 @@ package org.apache.asterix.graphix.function.rewrite;
 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 ElementEvaluationAnnotation(Kind kind) {
+        this.kind = kind;
+    }
+
+    public Kind getKind() {
+        return kind;
+    }
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
-    @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    public enum Kind {
+        EXPAND_AND_UNION,
+        SWITCH_AND_CYCLE
     }
 }
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/LoweringExemptAnnotation.java
similarity index 64%
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/LoweringExemptAnnotation.java
index ff00077..3f59a72 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/LoweringExemptAnnotation.java
@@ -16,16 +16,16 @@
  * 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 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();
 
-public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
-    @Override
-    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
-        return new GraphixASTPrintVisitor(writer);
+    private LoweringExemptAnnotation() {
     }
 }
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/annotation/SubqueryVertexJoinAnnotation.java
similarity index 53%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/SubqueryVertexJoinAnnotation.java
index aa939fb..8425b14 100644
--- 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/annotation/SubqueryVertexJoinAnnotation.java
@@ -16,23 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.resolve;
+package org.apache.asterix.graphix.lang.annotation;
 
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.rewrites.visitor.StructureResolutionVisitor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
 /**
- * @see StructureResolutionVisitor
+ * 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 interface IGraphElementResolver {
-    /**
-     * @param fromGraphClause FROM-GRAPH-CLAUSE to resolve edge & vertex labels, and edge directions for.
-     */
-    void resolve(FromGraphClause fromGraphClause) throws CompilationException;
+public class SubqueryVertexJoinAnnotation implements IExpressionAnnotation {
+    private final VariableExpr sourceVertexVariable;
 
-    /**
-     * @return True if we cannot resolve any more elements. False otherwise.
-     */
-    boolean isAtFixedPoint();
+    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 class FromGraphClause extends AbstractClause {
 
     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 @@ public class FromGraphClause extends AbstractClause {
         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 @@ public class FromGraphClause extends AbstractClause {
         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 java.util.stream.Collectors;
 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 class MatchClause extends AbstractClause {
 
     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 @@ public class MatchClause extends AbstractClause {
     @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/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
similarity index 74%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
index 78851df..ddc6d5f 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/extension/IGraphixVisitorExtension.java
@@ -16,13 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.clause.extension;
 
--- param max-warnings:string=2
+import org.apache.asterix.lang.common.base.IVisitorExtension;
 
-// There are two dangling vertices of different labels, and zero edges.
-FROM GRAPH  Yelp.YelpGraph
-MATCH       (u:User), (r:Review)
-SELECT      u.user_id,
-            r.review_id
-ORDER BY    u.user_id,
-            r.review_id;
+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.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.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 @@ public class EdgePatternExpr extends AbstractExpression {
         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 @@ public class EdgePatternExpr extends AbstractExpression {
         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 @@ public class EdgePatternExpr extends AbstractExpression {
         }
         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.Objects;
 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 @@ public class GraphConstructor extends AbstractExpression {
 
     /**
      * 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 @@ public class GraphConstructor extends AbstractExpression {
 
     /**
      * 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 @@ package org.apache.asterix.graphix.lang.expression;
 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 class PathPatternExpr extends AbstractExpression {
     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.Set;
 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 @@ public class VertexPatternExpr extends AbstractExpression {
         return labels;
     }
 
+    public Expression getFilterExpr() {
+        return filterExpr;
+    }
+
     public VariableExpr getVariableExpr() {
         return variableExpr;
     }
@@ -57,10 +68,8 @@ public class VertexPatternExpr extends AbstractExpression {
         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 @@ public class VertexPatternExpr extends AbstractExpression {
             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/rewrites/visitor/QueryKnowledgeVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphixParserHint.java
similarity index 52%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphixParserHint.java
index d56419b..95e7403 100644
--- 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/parser/GraphixParserHint.java
@@ -16,26 +16,30 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
+package org.apache.asterix.graphix.lang.parser;
 
-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.graphix.algebra.compiler.option.ElementEvaluationOption;
 
 /**
- * @see QueryKnowledgeTable
+ * 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 class QueryKnowledgeVisitor extends AbstractGraphixQueryVisitor {
-    private final QueryKnowledgeTable queryKnowledgeTable = new QueryKnowledgeTable();
+public enum GraphixParserHint {
+    EXPAND_AND_UNION_HINT(ElementEvaluationOption.EXPAND_AND_UNION.getOptionValue()),
+    SWITCH_AND_CYCLE_HINT(ElementEvaluationOption.SWITCH_AND_CYCLE.getOptionValue());
 
-    @Override
-    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-        queryKnowledgeTable.put(vertexPatternExpr);
-        return vertexPatternExpr;
+    private final String id;
+
+    GraphixParserHint(String id) {
+        this.id = id;
     }
 
-    public QueryKnowledgeTable getQueryKnowledgeTable() {
-        return queryKnowledgeTable;
+    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%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
copy 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/rewrites/common/ElementLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/ElementLookupTable.java
similarity index 58%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/common/ElementLookupTable.java
index cfa4aae..bf1eb62 100644
--- 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/rewrite/common/ElementLookupTable.java
@@ -16,53 +16,55 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.common;
+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.GraphElementIdentifier;
+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 GraphElementIdentifier} instances.
+ * keys-- indexed by {@link IElementIdentifier} 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<>();
+    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(GraphElementIdentifier identifier, GraphElementDeclaration graphElementDeclaration) {
+    public void put(IElementIdentifier identifier, GraphElementDeclaration graphElementDeclaration) {
         graphElementDeclMap.put(identifier, graphElementDeclaration);
     }
 
-    public void putVertexKey(GraphElementIdentifier identifier, List<List<String>> primaryKey) {
+    public void putVertexKey(VertexIdentifier identifier, List<List<String>> primaryKey) {
         vertexKeyMap.put(identifier, primaryKey);
     }
 
-    public void putEdgeKeys(GraphElementIdentifier identifier, List<List<String>> sourceKey,
+    public void putEdgeKeys(EdgeIdentifier identifier, List<List<String>> sourceKey,
             List<List<String>> destinationKey) {
         edgeSourceKeysMap.put(identifier, sourceKey);
         edgeDestKeysMap.put(identifier, destinationKey);
     }
 
-    public GraphElementDeclaration getElementDecl(GraphElementIdentifier identifier) {
+    public GraphElementDeclaration getElementDecl(IElementIdentifier identifier) {
         return graphElementDeclMap.get(identifier);
     }
 
-    public List<List<String>> getVertexKey(GraphElementIdentifier identifier) {
+    public List<List<String>> getVertexKey(VertexIdentifier identifier) {
         return vertexKeyMap.get(identifier);
     }
 
-    public List<List<String>> getEdgeDestKey(GraphElementIdentifier identifier) {
+    public List<List<String>> getEdgeDestKey(EdgeIdentifier identifier) {
         return edgeDestKeysMap.get(identifier);
     }
 
-    public List<List<String>> getEdgeSourceKey(GraphElementIdentifier identifier) {
+    public List<List<String>> getEdgeSourceKey(EdgeIdentifier identifier) {
         return edgeSourceKeysMap.get(identifier);
     }
 
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/rewrites/lower/action/AbstractInlineAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/AbstractInlineAction.java
similarity index 53%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/AbstractInlineAction.java
index b82801b..da91946 100644
--- 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/rewrite/lower/action/AbstractInlineAction.java
@@ -16,20 +16,20 @@
  * 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 static org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+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.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.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;
@@ -39,61 +39,66 @@ 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.
+ * 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 VarIdentifier elementVariable;
-
-    // The following is reset on each application.
-    private VariableSubstitutionVisitor substitutionVisitor;
+    protected VariableExpr elementVariable;
 
     protected AbstractInlineAction(GraphixRewritingContext graphixRewritingContext,
-            ElementBodyAnalysisContext bodyAnalysisContext, VarIdentifier elementVariable) {
+            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.
-        substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
+        remapCloneVisitor.resetSubstitutions();
         if (bodyAnalysisContext.getFromTermVariable() != null) {
             VariableExpr fromTermVariableExpr = bodyAnalysisContext.getFromTermVariable();
-            VariableExpr elementVariableExpr = new VariableExpr(elementVariable);
-            substitutionVisitor.addSubstitution(fromTermVariableExpr.getVar(), elementVariableExpr);
+            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()) {
-                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);
+                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);
                 });
             }
         }
@@ -101,17 +106,15 @@ public abstract class AbstractInlineAction implements IEnvironmentAction {
         // 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);
+                LetClause copiedClause = (LetClause) remapCloneVisitor.substitute(letClause);
+                VariableExpr reboundLetVariable = substitutionAdder.apply(copiedClause.getVarExpr());
+                VariableExpr reboundLetVariableCopy = deepCopyVisitor.visit(reboundLetVariable, 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));
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    LetClause reboundLetClause = new LetClause(reboundLetVariableCopy, copiedBindingExpr);
+                    reboundLetClause.setSourceLocation(letClause.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(reboundLetClause);
                 });
             }
         }
@@ -119,11 +122,11 @@ public abstract class AbstractInlineAction implements IEnvironmentAction {
         // 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);
+                WhereClause copiedClause = (WhereClause) remapCloneVisitor.substitute(whereClause);
+                loweringEnvironment.acceptTransformer(lowerList -> {
+                    WhereClause newWhereClause = new WhereClause(copiedClause.getWhereExpr());
+                    newWhereClause.setSourceLocation(whereClause.getSourceLocation());
+                    lowerList.addNonRepresentativeClause(newWhereClause);
                 });
             }
         }
@@ -135,9 +138,8 @@ public abstract class AbstractInlineAction implements IEnvironmentAction {
             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));
+                ILangExpression fieldValueExpr = remapCloneVisitor.substitute(projection.getExpression());
+                fieldBindings.add(new FieldBinding(fieldNameExpr, (Expression) fieldValueExpr));
             }
             return new RecordConstructor(fieldBindings);
 
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 88%
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/canonical/ICanonicalExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/lower/action/ILowerListTransformer.java
similarity index 75%
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/lower/action/ILowerListTransformer.java
index 65f42e5..72a48f4 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/lower/action/ILowerListTransformer.java
@@ -16,14 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.canonical;
-
-import java.util.List;
+package org.apache.asterix.graphix.lang.rewrite.lower.action;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.rewrite.lower.struct.ClauseCollection;
 
 @FunctionalInterface
-public interface ICanonicalExpander<T> {
-    void apply(T patternExpr, List<GraphSelectBlock> inputSelectBlocks) 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.List;
 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 @@ import org.apache.asterix.lang.common.literal.StringLiteral;
  * 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 @@ public class PathPatternAction implements IEnvironmentAction {
     @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 @@ public class PathPatternAction implements IEnvironmentAction {
         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 @@ public class PathPatternAction implements IEnvironmentAction {
         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 java.util.stream.Collectors;
 
 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 @@ public class GraphixASTPrintVisitor extends SqlppAstPrintVisitor implements IGra
     }
 
     @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.OrderbyClause;
 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.RecordConstructor;
 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.struct.SetOperationInput;
 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 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
 
     @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 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
             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 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
             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 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
 
     @Override
     public String visit(VariableExpr variableExpr, Void arg) {
-        return "`" + formatIdentifierForQuery(variableExpr.getVar().getValue()) + "`";
+        return "`" + formatIdentifierForQuery(variableExpr.getVar()) + "`";
     }
 
     @Override
@@ -560,8 +610,8 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
 
     @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
@@ -594,6 +644,12 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
         return sb.toString();
     }
 
+    @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();
@@ -665,7 +721,7 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
 
     @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 @@ public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisit
             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/visitor/ILetCorrelateClauseVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IPatternGroupResolver.java
similarity index 72%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/resolve/IPatternGroupResolver.java
index dd8a89d..c0129fb 100644
--- 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/rewrite/resolve/IPatternGroupResolver.java
@@ -16,12 +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.rewrite.resolve;
 
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.CorrLetClause;
-import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.graphix.lang.struct.PatternGroup;
 
-public interface ILetCorrelateClauseVisitor<R, T> extends ILangVisitor<R, T> {
-    R visit(CorrLetClause lcc, T arg) throws CompilationException;
+@FunctionalInterface
+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 final class LowerRewritingUtil {
 
     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.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.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 @@ import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 /**
  * 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 @@ public class ElementBodyAnalysisVisitor extends AbstractSqlppSimpleExpressionVis
     @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 @@ public class ElementBodyAnalysisVisitor extends AbstractSqlppSimpleExpressionVis
             // 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 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.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.EdgePatternExpr;
 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 @@ import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 /**
  * 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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
     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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
         }
 
         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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
                 }
 
                 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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
             }
         }
         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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
                     "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 @@ public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
             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 @@ public class FunctionResolutionVisitor extends AbstractGraphixQueryVisitor {
         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 java.util.Set;
 
 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.GraphDropStatement;
 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 @@ import org.apache.asterix.lang.sqlpp.visitor.DeepCopyVisitor;
  */
 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 @@ public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixL
         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 @@ public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixL
         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 @@ public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixL
         }
 
         // 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 @@ public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixL
         // 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 @@ public class GraphixDeepCopyVisitor extends DeepCopyVisitor implements IGraphixL
         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.GraphixFunctionIdentifiers;
 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/rewrites/visitor/PopulateUnknownsVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PopulateUnknownsVisitor.java
similarity index 53%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrite/visitor/PopulateUnknownsVisitor.java
index 7da0df5..37f341f 100644
--- 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/rewrite/visitor/PopulateUnknownsVisitor.java
@@ -14,21 +14,23 @@
  * 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;
 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.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.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;
@@ -36,8 +38,10 @@ 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.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;
@@ -45,18 +49,20 @@ 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.
+ * <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<VarIdentifier> newVariableSupplier;
+    private final Supplier<VariableExpr> newVariableSupplier;
 
     public PopulateUnknownsVisitor(GraphixRewritingContext graphixRewritingContext) {
         generateColumnNameVisitor = new GenerateColumnNameVisitor(graphixRewritingContext);
-        newVariableSupplier = graphixRewritingContext::getNewGraphixVariable;
+        newVariableSupplier = () -> new VariableExpr(graphixRewritingContext.newVariable());
     }
 
     @Override
@@ -66,61 +72,59 @@ public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
     }
 
     @Override
-    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        super.visit(graphSelectBlock, arg);
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        super.visit(selectBlock, arg);
 
-        if (graphSelectBlock.hasGroupbyClause()) {
+        if (selectBlock.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);
+            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 (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
-                        VarIdentifier edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(edgeVariable)) {
-                            userLiveVariables.add(edgeVariable);
+                        for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                            userLiveVariables.add(vertexExpression.getVariableExpr());
+                        }
+                        for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                            userLiveVariables.add(edgeExpression.getEdgeDescriptor().getVariableExpr());
                         }
                     }
                 }
-            }
-            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 (!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 (graphSelectBlock.hasLetWhereClauses()) {
-                for (AbstractClause abstractClause : graphSelectBlock.getLetWhereList()) {
+            if (selectBlock.hasLetWhereClauses()) {
+                for (AbstractClause abstractClause : selectBlock.getLetWhereList()) {
                     if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
                         LetClause letClause = (LetClause) abstractClause;
-                        VarIdentifier bindingVariable = letClause.getVarExpr().getVar();
-                        if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
-                            userLiveVariables.add(bindingVariable);
-                        }
+                        userLiveVariables.add(letClause.getVarExpr());
                     }
                 }
             }
 
             // 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)));
+            for (VariableExpr userLiveVariable : userLiveVariables) {
+                String variableName = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getVar().getValue());
+                newGroupFieldList.add(new Pair<>(userLiveVariable, new Identifier(variableName)));
             }
-            graphSelectBlock.getGroupbyClause().setGroupFieldList(newGroupFieldList);
+            selectBlock.getGroupbyClause().setGroupFieldList(newGroupFieldList);
         }
         return null;
     }
@@ -128,7 +132,7 @@ public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
     @Override
     public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
         if (vertexExpression.getVariableExpr() == null) {
-            vertexExpression.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
+            vertexExpression.setVariableExpr(newVariableSupplier.get());
         }
         return super.visit(vertexExpression, arg);
     }
@@ -137,7 +141,7 @@ public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
     public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
         EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
         if (edgeDescriptor.getVariableExpr() == null) {
-            edgeDescriptor.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
+            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 @@ import org.apache.hyracks.algebricks.common.utils.Pair;
 
 /**
  * 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;
-
-    public PostCanonicalizationVisitor(GraphixRewritingContext graphixRewritingContext,
-            GraphSelectBlock selectBlockExpansionSource, Set<SetOperationInput> generatedSetOpInputs,
-            List<VarIdentifier> userLiveVariables) {
+    private final Collection<SelectBlock> generatedSelectBlocks;
+    private final Collection<VariableExpr> sourceSelectLiveVariables;
+    private final SelectBlock selectBlockExpansionSource;
+
+    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 @@ public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
                 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,32 +136,32 @@ public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
         // 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);
             }
         }
         return null;
     }
 
-    @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()) {
@@ -170,10 +178,10 @@ public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
     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 @@ public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
     }
 
     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 @@ public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
         @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 90%
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.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.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.expression.WindowExpression;
 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 @@ public class PostRewriteCheckVisitor extends AbstractSqlppSimpleExpressionVisito
     }
 
     @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);
... 8240 lines suppressed ...