[NO-ISSUE][GRAPHIX] Updating rewriter for Graphix.

Details:
- Rewriter now generates SQLPP ASTs that a) minimize the nesting to
  reduce the time to compile and b) maximize the number of hash
  JOINs. This is accomplished by aiming for "wide" ASTs (via UNION-ALLs)
  as opposed to nested SELECT-EXPRs.
- Rewriter now pushes isomorphism conjuncts earlier (via a "correlated
  WHERE clause").
- Schema information for functions are now added in lazy manner, as
  opposed to eagerly.
- Added a DECLARE GRAPH statement.
- Added a SOURCE_VERTEX and DEST_VERTEX function.
- Fixed a bug w/ the element resolver not handling sub-paths properly.

Change-Id: I95c56b38e7a01e6f73fe59c2243f88646735ed39
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb-graph/+/16543
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Glenn Galvizo <ggalvizo@uci.edu>
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixRuleSetFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixRuleSetFactory.java
index d37661c..5ab94a3 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixRuleSetFactory.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixRuleSetFactory.java
@@ -26,6 +26,7 @@
 import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.core.rewriter.base.AbstractRuleController;
 import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
+import org.apache.hyracks.algebricks.core.rewriter.base.IRuleSetKind;
 
 public class GraphixRuleSetFactory implements IRuleSetFactory {
     @Override
@@ -35,6 +36,17 @@
     }
 
     @Override
+    public List<Pair<AbstractRuleController, List<IAlgebraicRewriteRule>>> getLogicalRewrites(IRuleSetKind ruleSetKind,
+            ICcApplicationContext appCtx) {
+        if (ruleSetKind == RuleSetKind.SAMPLING) {
+            return DefaultRuleSetFactory.buildLogicalSampling();
+
+        } else {
+            throw new IllegalArgumentException(String.valueOf(ruleSetKind));
+        }
+    }
+
+    @Override
     public List<Pair<AbstractRuleController, List<IAlgebraicRewriteRule>>> getPhysicalRewrites(
             ICcApplicationContext appCtx) {
         return DefaultRuleSetFactory.buildPhysical(appCtx);
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 9644cef..c342b10 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
@@ -35,8 +35,9 @@
 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.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.util.GraphStatementHandlingUtil;
 import org.apache.asterix.graphix.metadata.entity.dependency.DependencyIdentifier;
 import org.apache.asterix.graphix.metadata.entity.dependency.FunctionRequirements;
@@ -50,8 +51,10 @@
 import org.apache.asterix.lang.common.statement.CreateViewStatement;
 import org.apache.asterix.lang.common.statement.DataverseDropStatement;
 import org.apache.asterix.lang.common.statement.DropDatasetStatement;
+import org.apache.asterix.lang.common.statement.FunctionDecl;
 import org.apache.asterix.lang.common.statement.FunctionDropStatement;
 import org.apache.asterix.lang.common.statement.SynonymDropStatement;
+import org.apache.asterix.lang.common.statement.ViewDecl;
 import org.apache.asterix.lang.common.statement.ViewDropStatement;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.metadata.MetadataTransactionContext;
@@ -59,8 +62,11 @@
 import org.apache.asterix.translator.IRequestParameters;
 import org.apache.asterix.translator.SessionOutput;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public class GraphixQueryTranslator extends QueryTranslator {
+    Set<DeclareGraphStatement> declareGraphStatements = new HashSet<>();
+
     public GraphixQueryTranslator(ICcApplicationContext appCtx, List<Statement> statements, SessionOutput output,
             ILangCompilationProvider compilationProvider, ExecutorService executorService,
             IResponsePrinter responsePrinter) {
@@ -71,12 +77,23 @@
         return (GraphixQueryRewriter) rewriterFactory.createQueryRewriter();
     }
 
-    public void setGraphElementNormalizedBody(MetadataProvider metadataProvider, GraphElementDecl graphElementDecl,
-            GraphixQueryRewriter queryRewriter) throws CompilationException {
-        LangRewritingContext langRewritingContext =
-                new LangRewritingContext(metadataProvider, declaredFunctions, null, warningCollector, 0);
-        GraphixRewritingContext graphixRewritingContext = new GraphixRewritingContext(langRewritingContext);
-        queryRewriter.loadNormalizedGraphElement(graphixRewritingContext, graphElementDecl);
+    public void addDeclaredGraph(DeclareGraphStatement declareGraphStatement) {
+        declareGraphStatements.add(declareGraphStatement);
+    }
+
+    public void setGraphElementNormalizedBody(MetadataProvider metadataProvider,
+            GraphElementDeclaration graphElementDeclaration, GraphixQueryRewriter queryRewriter)
+            throws CompilationException {
+        queryRewriter.loadNormalizedGraphElement((GraphixRewritingContext) createLangRewritingContext(metadataProvider,
+                declaredFunctions, null, warningCollector, 0), graphElementDeclaration);
+    }
+
+    @Override
+    protected LangRewritingContext createLangRewritingContext(MetadataProvider metadataProvider,
+            List<FunctionDecl> declaredFunctions, List<ViewDecl> declaredViews, IWarningCollector warningCollector,
+            int varCounter) {
+        return new GraphixRewritingContext(metadataProvider, declaredFunctions, declaredViews, declareGraphStatements,
+                warningCollector, varCounter);
     }
 
     /**
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/FunctionRewriteMap.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/FunctionRewriteMap.java
deleted file mode 100644
index 0e9504c..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/FunctionRewriteMap.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.function;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.function.rewrite.EdgeIfFunctionRewrite;
-import org.apache.asterix.graphix.function.rewrite.IFunctionRewrite;
-import org.apache.asterix.graphix.function.rewrite.PathEdgesFunctionRewrite;
-import org.apache.asterix.graphix.function.rewrite.PathHopCountFunctionRewrite;
-import org.apache.asterix.graphix.function.rewrite.PathLabelsFunctionRewrite;
-import org.apache.asterix.graphix.function.rewrite.PathVerticesFunctionRewrite;
-import org.apache.asterix.graphix.lang.rewrites.record.EdgeRecord;
-import org.apache.asterix.graphix.lang.rewrites.record.IElementRecord;
-import org.apache.asterix.graphix.lang.rewrites.record.VertexRecord;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixFunctionCallVisitor;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.literal.StringLiteral;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-
-/**
- * @see GraphixFunctionCallVisitor
- */
-public class FunctionRewriteMap {
-    private final static Map<FunctionIdentifier, IFunctionRewrite> graphixFunctionMap;
-
-    static {
-        graphixFunctionMap = new HashMap<>();
-
-        // Add our element function rewrites.
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.ELEMENT_LABEL, (c, a) -> {
-            final Identifier detailSuffix = new Identifier(IElementRecord.ELEMENT_DETAIL_NAME);
-            final Identifier labelSuffix = new Identifier(IElementRecord.ELEMENT_LABEL_FIELD_NAME);
-            FieldAccessor detailAccess = new FieldAccessor(a.get(0), detailSuffix);
-            return new FieldAccessor(detailAccess, labelSuffix);
-        });
-
-        // Add our vertex function rewrites.
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.VERTEX_PROPERTIES, (c, a) -> {
-            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_REMOVE);
-            LiteralExpr elementDetailName = new LiteralExpr(new StringLiteral(IElementRecord.ELEMENT_DETAIL_NAME));
-            LiteralExpr vertexDetailName = new LiteralExpr(new StringLiteral(VertexRecord.VERTEX_DETAIL_NAME));
-            CallExpr removeElementDetailExpr = new CallExpr(functionSignature, List.of(a.get(0), elementDetailName));
-            return new CallExpr(functionSignature, List.of(removeElementDetailExpr, vertexDetailName));
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.VERTEX_KEY, (c, a) -> {
-            final Identifier detailSuffix = new Identifier(VertexRecord.VERTEX_DETAIL_NAME);
-            final Identifier keySuffix = new Identifier(VertexRecord.PRIMARY_KEY_FIELD_NAME);
-            FieldAccessor detailAccess = new FieldAccessor(a.get(0), detailSuffix);
-            return new FieldAccessor(detailAccess, keySuffix);
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.VERTEX_DETAIL, (c, a) -> {
-            final Identifier elementDetailSuffix = new Identifier(IElementRecord.ELEMENT_DETAIL_NAME);
-            final Identifier vertexDetailSuffix = new Identifier(VertexRecord.VERTEX_DETAIL_NAME);
-            FieldAccessor elementDetailAccess = new FieldAccessor(a.get(0), elementDetailSuffix);
-            FieldAccessor vertexDetailAccess = new FieldAccessor(a.get(0), vertexDetailSuffix);
-            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_MERGE);
-            return new CallExpr(functionSignature, List.of(elementDetailAccess, vertexDetailAccess));
-        });
-
-        // Add our edge function rewrites.
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_PROPERTIES, (c, a) -> {
-            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_REMOVE);
-            LiteralExpr elementDetailName = new LiteralExpr(new StringLiteral(IElementRecord.ELEMENT_DETAIL_NAME));
-            LiteralExpr edgeDetailName = new LiteralExpr(new StringLiteral(EdgeRecord.EDGE_DETAIL_NAME));
-            CallExpr removeElementDetailExpr = new CallExpr(functionSignature, List.of(a.get(0), elementDetailName));
-            return new CallExpr(functionSignature, List.of(removeElementDetailExpr, edgeDetailName));
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_SOURCE_KEY, (c, a) -> {
-            final Identifier detailSuffix = new Identifier(EdgeRecord.EDGE_DETAIL_NAME);
-            final Identifier sourceKeySuffix = new Identifier(EdgeRecord.SOURCE_KEY_FIELD_NAME);
-            FieldAccessor detailAccess = new FieldAccessor(a.get(0), detailSuffix);
-            return new FieldAccessor(detailAccess, sourceKeySuffix);
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_DEST_KEY, (c, a) -> {
-            final Identifier detailSuffix = new Identifier(EdgeRecord.EDGE_DETAIL_NAME);
-            final Identifier destKeySuffix = new Identifier(EdgeRecord.DEST_KEY_FIELD_NAME);
-            FieldAccessor detailAccess = new FieldAccessor(a.get(0), detailSuffix);
-            return new FieldAccessor(detailAccess, destKeySuffix);
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_DIRECTION, (c, a) -> {
-            final Identifier detailSuffix = new Identifier(EdgeRecord.EDGE_DETAIL_NAME);
-            final Identifier directionSuffix = new Identifier(EdgeRecord.DIRECTION_FIELD_NAME);
-            FieldAccessor detailAccess = new FieldAccessor(a.get(0), detailSuffix);
-            return new FieldAccessor(detailAccess, directionSuffix);
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_DETAIL, (c, a) -> {
-            final Identifier elementDetailSuffix = new Identifier(IElementRecord.ELEMENT_DETAIL_NAME);
-            final Identifier edgeDetailSuffix = new Identifier(EdgeRecord.EDGE_DETAIL_NAME);
-            FieldAccessor elementDetailAccess = new FieldAccessor(a.get(0), elementDetailSuffix);
-            FieldAccessor edgeDetailAccess = new FieldAccessor(a.get(0), edgeDetailSuffix);
-            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_MERGE);
-            return new CallExpr(functionSignature, List.of(elementDetailAccess, edgeDetailAccess));
-        });
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_LEFT_TO_RIGHT_IF, new EdgeIfFunctionRewrite(true));
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_RIGHT_TO_LEFT_IF, new EdgeIfFunctionRewrite(false));
-
-        // Add our path function rewrites.
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.PATH_HOP_COUNT, new PathHopCountFunctionRewrite());
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.PATH_LABELS, new PathLabelsFunctionRewrite());
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.PATH_VERTICES, new PathVerticesFunctionRewrite());
-        graphixFunctionMap.put(GraphixFunctionIdentifiers.PATH_EDGES, new PathEdgesFunctionRewrite());
-    }
-
-    public static IFunctionRewrite getFunctionRewrite(FunctionIdentifier functionIdentifier) {
-        return graphixFunctionMap.getOrDefault(functionIdentifier, null);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionAliases.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionAliases.java
index b82041e..2526550 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionAliases.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionAliases.java
@@ -33,19 +33,16 @@
         functionAliasMap.put("label", GraphixFunctionIdentifiers.ELEMENT_LABEL);
 
         // Build aliases for edge functions.
-        functionAliasMap.put("source-key", GraphixFunctionIdentifiers.EDGE_SOURCE_KEY);
-        functionAliasMap.put("dest-key", GraphixFunctionIdentifiers.EDGE_DEST_KEY);
-        functionAliasMap.put("edge-destination-key", GraphixFunctionIdentifiers.EDGE_DEST_KEY);
-        functionAliasMap.put("destination-key", GraphixFunctionIdentifiers.EDGE_DEST_KEY);
         functionAliasMap.put("dir", GraphixFunctionIdentifiers.EDGE_DIRECTION);
         functionAliasMap.put("direction", GraphixFunctionIdentifiers.EDGE_DIRECTION);
-        functionAliasMap.put("left-to-right-if", GraphixFunctionIdentifiers.EDGE_LEFT_TO_RIGHT_IF);
-        functionAliasMap.put("right-to-left-if", GraphixFunctionIdentifiers.EDGE_RIGHT_TO_LEFT_IF);
+        functionAliasMap.put("source-vertex", GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX);
+        functionAliasMap.put("dest-vertex", GraphixFunctionIdentifiers.EDGE_DEST_VERTEX);
+        functionAliasMap.put("edge-destination-vertex", GraphixFunctionIdentifiers.EDGE_DEST_VERTEX);
+        functionAliasMap.put("destination-vertex", GraphixFunctionIdentifiers.EDGE_DEST_VERTEX);
 
         // Build aliases for path functions.
         functionAliasMap.put("hop-count", GraphixFunctionIdentifiers.PATH_HOP_COUNT);
         functionAliasMap.put("edge-count", GraphixFunctionIdentifiers.PATH_HOP_COUNT);
-        functionAliasMap.put("labels", GraphixFunctionIdentifiers.PATH_LABELS);
         functionAliasMap.put("vertices", GraphixFunctionIdentifiers.PATH_VERTICES);
         functionAliasMap.put("edges", GraphixFunctionIdentifiers.PATH_EDGES);
     }
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 eee4a1c..50e58b9 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
@@ -19,7 +19,9 @@
 package org.apache.asterix.graphix.function;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Consumer;
 
 import org.apache.asterix.common.metadata.DataverseName;
@@ -27,6 +29,9 @@
 
 public class GraphixFunctionIdentifiers {
     private static final Map<String, FunctionIdentifier> functionIdentifierMap;
+    private static final Set<FunctionIdentifier> vertexFunctionSet;
+    private static final Set<FunctionIdentifier> edgeFunctionSet;
+    private static final Set<FunctionIdentifier> pathFunctionSet;
 
     // Graphix functions should exist separate from the "ASTERIX_DV" dataverse.
     public static final DataverseName GRAPHIX_DV = DataverseName.createBuiltinDataverseName("graphix");
@@ -43,28 +48,18 @@
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "element-label", 1);
 
     // Functions that can be called on vertices.
-    public static final FunctionIdentifier VERTEX_PROPERTIES =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "vertex-properties", 1);
     public static final FunctionIdentifier VERTEX_DETAIL =
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "vertex-detail", 1);
-    public static final FunctionIdentifier VERTEX_KEY =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "vertex-key", 1);
 
     // Functions that can be called on edges.
-    public static final FunctionIdentifier EDGE_PROPERTIES =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-properties", 1);
     public static final FunctionIdentifier EDGE_DETAIL =
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-detail", 1);
-    public static final FunctionIdentifier EDGE_SOURCE_KEY =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-source-key", 1);
-    public static final FunctionIdentifier EDGE_DEST_KEY =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-dest-key", 1);
     public static final FunctionIdentifier EDGE_DIRECTION =
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-direction", 1);
-    public static final FunctionIdentifier EDGE_LEFT_TO_RIGHT_IF =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-left-to-right-if", 3);
-    public static final FunctionIdentifier EDGE_RIGHT_TO_LEFT_IF =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-right-to-left-if", 3);
+    public static final FunctionIdentifier EDGE_SOURCE_VERTEX =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-source-vertex", 1);
+    public static final FunctionIdentifier EDGE_DEST_VERTEX =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-dest-vertex", 1);
 
     // Functions that can be called on paths.
     public static final FunctionIdentifier PATH_HOP_COUNT =
@@ -73,28 +68,46 @@
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "path-vertices", 1);
     public static final FunctionIdentifier PATH_EDGES =
             new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "path-edges", 1);
-    public static final FunctionIdentifier PATH_LABELS =
-            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "path-labels", 1);
 
     static {
-        functionIdentifierMap = new HashMap<>();
-
         // Register all the functions above.
+        functionIdentifierMap = new HashMap<>();
         Consumer<FunctionIdentifier> functionRegister = f -> functionIdentifierMap.put(f.getName(), f);
         functionRegister.accept(ELEMENT_LABEL);
-        functionRegister.accept(VERTEX_PROPERTIES);
         functionRegister.accept(VERTEX_DETAIL);
-        functionRegister.accept(VERTEX_KEY);
-        functionRegister.accept(EDGE_PROPERTIES);
         functionRegister.accept(EDGE_DETAIL);
-        functionRegister.accept(EDGE_SOURCE_KEY);
-        functionRegister.accept(EDGE_DEST_KEY);
         functionRegister.accept(EDGE_DIRECTION);
-        functionRegister.accept(EDGE_LEFT_TO_RIGHT_IF);
-        functionRegister.accept(EDGE_RIGHT_TO_LEFT_IF);
+        functionRegister.accept(EDGE_SOURCE_VERTEX);
+        functionRegister.accept(EDGE_DEST_VERTEX);
         functionRegister.accept(PATH_HOP_COUNT);
         functionRegister.accept(PATH_VERTICES);
         functionRegister.accept(PATH_EDGES);
-        functionRegister.accept(PATH_LABELS);
+
+        // Divide our functions into their respective input types.
+        vertexFunctionSet = new HashSet<>();
+        edgeFunctionSet = new HashSet<>();
+        pathFunctionSet = new HashSet<>();
+        vertexFunctionSet.add(ELEMENT_LABEL);
+        vertexFunctionSet.add(VERTEX_DETAIL);
+        edgeFunctionSet.add(ELEMENT_LABEL);
+        edgeFunctionSet.add(EDGE_DETAIL);
+        edgeFunctionSet.add(EDGE_DIRECTION);
+        edgeFunctionSet.add(EDGE_SOURCE_VERTEX);
+        edgeFunctionSet.add(EDGE_DEST_VERTEX);
+        pathFunctionSet.add(PATH_HOP_COUNT);
+        pathFunctionSet.add(PATH_VERTICES);
+        pathFunctionSet.add(PATH_EDGES);
+    }
+
+    public static boolean isVertexFunction(FunctionIdentifier functionIdentifier) {
+        return vertexFunctionSet.contains(functionIdentifier);
+    }
+
+    public static boolean isEdgeFunction(FunctionIdentifier functionIdentifier) {
+        return edgeFunctionSet.contains(functionIdentifier);
+    }
+
+    public static boolean isPathFunction(FunctionIdentifier functionIdentifier) {
+        return pathFunctionSet.contains(functionIdentifier);
     }
 }
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
new file mode 100644
index 0000000..d1f3c89
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionMap.java
@@ -0,0 +1,81 @@
+/*
+ * 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.function;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+
+import org.apache.asterix.graphix.function.prepare.EdgeDestVertexPrepare;
+import org.apache.asterix.graphix.function.prepare.EdgeDetailPrepare;
+import org.apache.asterix.graphix.function.prepare.EdgeDirectionPrepare;
+import org.apache.asterix.graphix.function.prepare.EdgeSourceVertexPrepare;
+import org.apache.asterix.graphix.function.prepare.ElementLabelPrepare;
+import org.apache.asterix.graphix.function.prepare.IFunctionPrepare;
+import org.apache.asterix.graphix.function.prepare.VertexDetailPrepare;
+import org.apache.asterix.graphix.function.rewrite.IFunctionRewrite;
+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.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
+ */
+public class GraphixFunctionMap {
+    private final static Map<FunctionIdentifier, IFunctionPrepare> graphixFunctionPrepareMap;
+    private final static Map<FunctionIdentifier, IFunctionRewrite> graphixFunctionRewriteMap;
+
+    static {
+        graphixFunctionRewriteMap = new HashMap<>();
+        graphixFunctionPrepareMap = new HashMap<>();
+
+        // Add all of our function rewrites.
+        BiConsumer<FunctionIdentifier, Identifier> rewriteInserter =
+                (f, i) -> graphixFunctionRewriteMap.put(f, new SchemaAccessRewrite(f, i));
+        rewriteInserter.accept(GraphixFunctionIdentifiers.ELEMENT_LABEL, ElementLabelPrepare.IDENTIFIER);
+        rewriteInserter.accept(GraphixFunctionIdentifiers.VERTEX_DETAIL, VertexDetailPrepare.IDENTIFIER);
+        rewriteInserter.accept(GraphixFunctionIdentifiers.EDGE_DETAIL, EdgeDetailPrepare.IDENTIFIER);
+        rewriteInserter.accept(GraphixFunctionIdentifiers.EDGE_DIRECTION, EdgeDirectionPrepare.IDENTIFIER);
+        rewriteInserter.accept(GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX, EdgeSourceVertexPrepare.IDENTIFIER);
+        rewriteInserter.accept(GraphixFunctionIdentifiers.EDGE_DEST_VERTEX, EdgeDestVertexPrepare.IDENTIFIER);
+        graphixFunctionRewriteMap.put(GraphixFunctionIdentifiers.PATH_HOP_COUNT, new PathHopCountRewrite());
+        graphixFunctionRewriteMap.put(GraphixFunctionIdentifiers.PATH_EDGES, new PathEdgesRewrite());
+        graphixFunctionRewriteMap.put(GraphixFunctionIdentifiers.PATH_VERTICES, new PathVerticesRewrite());
+
+        // Add all of our function prepares (schema enrichment functions).
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.ELEMENT_LABEL, new ElementLabelPrepare());
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.VERTEX_DETAIL, new VertexDetailPrepare());
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.EDGE_DETAIL, new EdgeDetailPrepare());
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.EDGE_DIRECTION, new EdgeDirectionPrepare());
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX, new EdgeSourceVertexPrepare());
+        graphixFunctionPrepareMap.put(GraphixFunctionIdentifiers.EDGE_DEST_VERTEX, new EdgeDestVertexPrepare());
+    }
+
+    public static IFunctionRewrite getFunctionRewrite(FunctionIdentifier functionIdentifier) {
+        return graphixFunctionRewriteMap.getOrDefault(functionIdentifier, null);
+    }
+
+    public static IFunctionPrepare getFunctionPrepare(FunctionIdentifier functionIdentifier) {
+        return graphixFunctionPrepareMap.getOrDefault(functionIdentifier, null);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionResolver.java
index 60c1f21..753e86c 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionResolver.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionResolver.java
@@ -19,31 +19,44 @@
 package org.apache.asterix.graphix.function;
 
 import java.util.Map;
+import java.util.function.BiFunction;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.functions.FunctionConstants;
 import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.util.CommonFunctionMapUtil;
+import org.apache.asterix.lang.common.util.FunctionUtil;
+import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
 import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.metadata.entities.Dataverse;
+import org.apache.asterix.metadata.entities.Function;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
  * Resolve any functions found in {@link GraphixFunctionIdentifiers}. If any user-defined functions have the same name
- * as a Graphix function, we defer resolution to the SQL++ rewriter. If we find a system defined function, we also
- * defer resolution to the SQL++ rewriter.
+ * as a Graphix function, we delegate resolution to the SQL++ resolver. If we find a system defined function, we also
+ * defer resolution to the SQL++ resolver.
  */
 public class GraphixFunctionResolver {
+    private final BiFunction<String, Integer, FunctionSignature> builtInFunctionResolver;
     private final Map<FunctionSignature, FunctionDecl> declaredFunctionMap;
     private final MetadataProvider metadataProvider;
 
     public GraphixFunctionResolver(MetadataProvider metadataProvider,
             Map<FunctionSignature, FunctionDecl> declaredFunctionMap) {
+        this.builtInFunctionResolver = FunctionUtil.createBuiltinFunctionResolver(metadataProvider);
         this.declaredFunctionMap = declaredFunctionMap;
         this.metadataProvider = metadataProvider;
     }
 
-    public FunctionSignature resolve(FunctionSignature functionSignature) throws CompilationException {
+    public FunctionSignature resolve(CallExpr callExpr, boolean allowNonStoredUDFCalls) throws CompilationException {
+        FunctionSignature functionSignature = callExpr.getFunctionSignature();
         DataverseName workingDataverseName = functionSignature.getDataverseName();
         if (workingDataverseName == null) {
             workingDataverseName = metadataProvider.getDefaultDataverseName();
@@ -64,28 +77,85 @@
             functionDecl = declaredFunctionMap.get(signatureWithDataverse);
 
             // If this has failed, retry with a variable number of arguments.
+            FunctionSignature signatureWithVarArgs = new FunctionSignature(workingDataverseName,
+                    functionSignature.getName(), FunctionIdentifier.VARARGS);
             if (functionDecl == null) {
-                FunctionSignature signatureWithVarArgs = new FunctionSignature(workingDataverseName,
-                        functionSignature.getName(), FunctionIdentifier.VARARGS);
                 functionDecl = declaredFunctionMap.get(signatureWithVarArgs);
             }
 
+            // We have found a function-declaration in our map. Return this...
             if (functionDecl != null) {
-                return null;
+                if (!allowNonStoredUDFCalls && !functionDecl.isStored()) {
+                    throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getFunctionSignature(),
+                            functionDecl.getSignature().toString());
+                }
+                return functionDecl.getSignature();
+            }
+
+            // ...otherwise, we need to search our metadata.
+            try {
+                Function function = metadataProvider.lookupUserDefinedFunction(signatureWithDataverse);
+                if (function == null) {
+                    function = metadataProvider.lookupUserDefinedFunction(signatureWithVarArgs);
+                }
+                if (function != null) {
+                    return function.getSignature();
+                }
+
+            } catch (AlgebricksException e) {
+                throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, e, callExpr.getSourceLocation(),
+                        functionSignature.toString());
+            }
+
+            // If the dataverse was specified, we also need to make sure that this dataverse exists.
+            if (functionSignature.getDataverseName() != null) {
+                Dataverse dataverse;
+                try {
+                    dataverse = metadataProvider.findDataverse(functionSignature.getDataverseName());
+                    if (dataverse == null) {
+                        throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, callExpr.getSourceLocation(),
+                                functionSignature.getDataverseName());
+                    }
+
+                } catch (AlgebricksException e) {
+                    throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e, callExpr.getSourceLocation(),
+                            functionSignature.getDataverseName());
+                }
             }
         }
 
-        // We could not find a declared user-defined function. See if this is a Graphix-function call.
-        String functionName = functionSignature.getName().toLowerCase().replaceAll("_", "-");
-        FunctionIdentifier graphixFunctionIdentifier = GraphixFunctionIdentifiers.getFunctionIdentifier(functionName);
+        // We could not find a user-defined function. See if this is a Graphix-function call.
+        String graphixName = functionSignature.getName().toLowerCase().replaceAll("_", "-");
+        FunctionIdentifier graphixFunctionIdentifier = GraphixFunctionIdentifiers.getFunctionIdentifier(graphixName);
         if (graphixFunctionIdentifier == null) {
-            graphixFunctionIdentifier = GraphixFunctionAliases.getFunctionIdentifier(functionName);
+            graphixFunctionIdentifier = GraphixFunctionAliases.getFunctionIdentifier(graphixName);
         }
         if (graphixFunctionIdentifier != null) {
             return new FunctionSignature(graphixFunctionIdentifier);
         }
 
-        // This is either a SQL++ built-in function or an error. Defer this to the SQL++ rewrites.
-        return null;
+        // This is neither a Graphix function nor a user-defined function. Attempt to resolve to a built-in function.
+        String builtInName = functionSignature.getName().toLowerCase();
+        String mappedName = CommonFunctionMapUtil.getFunctionMapping(builtInName);
+        if (mappedName != null) {
+            builtInName = mappedName;
+        }
+        int functionArity = functionSignature.getArity();
+        FunctionSignature builtInSignature = builtInFunctionResolver.apply(builtInName, functionArity);
+        if (builtInSignature != null) {
+            return builtInSignature;
+        }
+
+        // If we could not resolve this, try see if this is an aggregate function or window function.
+        builtInSignature = new FunctionSignature(FunctionConstants.ASTERIX_DV, builtInName, functionArity);
+        if (FunctionMapUtil.isSql92AggregateFunction(builtInSignature)
+                || FunctionMapUtil.isCoreAggregateFunction(builtInSignature)
+                || BuiltinFunctions.getWindowFunction(builtInSignature.createFunctionIdentifier()) != null) {
+            return builtInSignature;
+        }
+
+        // ...otherwise, this is an unknown function.
+        throw new CompilationException(ErrorCode.UNKNOWN_FUNCTION, callExpr.getSourceLocation(),
+                functionSignature.toString());
     }
 }
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
new file mode 100644
index 0000000..d93f670
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/AbstractElementPrepare.java
@@ -0,0 +1,115 @@
+/*
+ * 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.function.prepare;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+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.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+
+/**
+ * We want to attach all of our schema detail to this "_GraphixSchemaDetail" field. If we are given a
+ * {@link RecordConstructor}, then we just add a new field binding. If we are given anything else, we need to wrap our
+ * input expression with a {@link BuiltinFunctions#RECORD_ADD} function call that includes this "_GraphixSchemaDetail"
+ * field.
+ */
+public abstract class AbstractElementPrepare implements IFunctionPrepare {
+    public static final Identifier GRAPHIX_SCHEMA_IDENTIFIER = new Identifier("_GraphixSchemaDetail");
+
+    // We need to pass this info down to our child.
+    protected ElementLookupTable elementLookupTable;
+    protected GraphIdentifier graphIdentifier;
+
+    protected abstract void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr)
+            throws CompilationException;
+
+    @Override
+    public Expression prepare(Expression sourceExpr, Expression inputExpr, GraphIdentifier graphIdentifier,
+            ElementLookupTable elementLookupTable) throws CompilationException {
+        this.elementLookupTable = elementLookupTable;
+        this.graphIdentifier = graphIdentifier;
+
+        // Transform our record expression.
+        if (sourceExpr.getKind() == Expression.Kind.RECORD_CONSTRUCTOR_EXPRESSION) {
+            RecordConstructor recordConstructor = (RecordConstructor) sourceExpr;
+            List<FieldBinding> fieldBindingList = recordConstructor.getFbList();
+            Optional<FieldBinding> schemaBinding = fieldBindingList.stream().filter(f -> {
+                LiteralExpr fieldNameExpr = (LiteralExpr) f.getLeftExpr();
+                StringLiteral fieldNameValue = (StringLiteral) fieldNameExpr.getValue();
+                return fieldNameValue.getStringValue().equals(GRAPHIX_SCHEMA_IDENTIFIER.getValue());
+            }).findFirst();
+
+            if (schemaBinding.isPresent()) {
+                // We have previously introduced schema detail into this expression. Add to our existing record.
+                FieldBinding schemaRecordBinding = schemaBinding.get();
+                RecordConstructor schemaRecord = (RecordConstructor) schemaRecordBinding.getRightExpr();
+                transformRecord(schemaRecord, inputExpr, sourceExpr);
+
+            } else {
+                // We need to introduce a schema detail record.
+                RecordConstructor schemaRecord = new RecordConstructor(new ArrayList<>());
+                schemaRecord.addHint(GraphixSchemaAnnotation.INSTANCE);
+                schemaRecord.setSourceLocation(sourceExpr.getSourceLocation());
+                transformRecord(schemaRecord, inputExpr, sourceExpr);
+                LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(GRAPHIX_SCHEMA_IDENTIFIER.getValue()));
+                FieldBinding newFieldBinding = new FieldBinding(fieldNameExpr, schemaRecord);
+                fieldBindingList.add(newFieldBinding);
+
+            }
+            return sourceExpr;
+
+        } else if (sourceExpr.getKind() == Expression.Kind.CALL_EXPRESSION) {
+            CallExpr callExpr = (CallExpr) sourceExpr;
+            if (callExpr.findHint(GraphixSchemaAnnotation.class) == GraphixSchemaAnnotation.INSTANCE) {
+                // We have a previously decorated Graphix CALL-EXPR. Add to this record.
+                RecordConstructor schemaRecord = (RecordConstructor) callExpr.getExprList().get(2);
+                transformRecord(schemaRecord, inputExpr, sourceExpr);
+                return sourceExpr;
+            }
+        }
+
+        // We have a non-record-constructor expression that we need to add a schema detail record to.
+        FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_ADD);
+        List<Expression> callExprArguments = new ArrayList<>();
+        RecordConstructor schemaRecord = new RecordConstructor(new ArrayList<>());
+        transformRecord(schemaRecord, inputExpr, sourceExpr);
+
+        // Finalize our new decorated schema detail record.
+        callExprArguments.add(sourceExpr);
+        callExprArguments.add(new LiteralExpr(new StringLiteral(GRAPHIX_SCHEMA_IDENTIFIER.getValue())));
+        callExprArguments.add(schemaRecord);
+        CallExpr callExpr = new CallExpr(functionSignature, callExprArguments);
+        callExpr.setSourceLocation(sourceExpr.getSourceLocation());
+        callExpr.addHint(GraphixSchemaAnnotation.INSTANCE);
+        return callExpr;
+    }
+}
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
new file mode 100644
index 0000000..7f95f28
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDestVertexPrepare.java
@@ -0,0 +1,50 @@
+/*
+ * 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.function.prepare;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+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;
+
+public class EdgeDestVertexPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("EdgeDestVertex");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr) {
+        if (!(inputExpr instanceof EdgePatternExpr)) {
+            return;
+        }
+        EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        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);
+        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
new file mode 100644
index 0000000..6efd635
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDetailPrepare.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.function.prepare;
+
+import static org.apache.asterix.lang.common.expression.ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR;
+
+import java.util.ArrayList;
+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.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.Expression;
+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.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class EdgeDetailPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("EdgeDetail");
+    public static final Identifier SOURCE_KEY_IDENTIFIER = new Identifier("SourceKey");
+    public static final Identifier DEST_KEY_IDENTIFIER = new Identifier("DestinationKey");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr)
+            throws CompilationException {
+        if (!(inputExpr instanceof EdgePatternExpr)) {
+            return;
+        }
+        EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+
+        // Insert our detail record into our schema.
+        RecordConstructor detailRecord = new RecordConstructor(new ArrayList<>());
+        LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
+        FieldBinding detailRecordBinding = new FieldBinding(fieldNameExpr, detailRecord);
+        schemaRecord.getFbList().add(detailRecordBinding);
+
+        // Insert our element-label into our detail record.
+        ElementLabelPrepare elementLabelPrepare = new ElementLabelPrepare();
+        elementLabelPrepare.transformRecord(detailRecord, inputExpr, sourceExpr);
+
+        // Insert our edge-direction into our detail record.
+        EdgeDirectionPrepare edgeDirectionPrepare = new EdgeDirectionPrepare();
+        edgeDirectionPrepare.transformRecord(detailRecord, inputExpr, sourceExpr);
+
+        // Insert our source-key into our detail record.
+        GraphElementIdentifier edgeIdentifier = edgeDescriptor.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());
+        ListConstructor sourceKeyFieldValueExpr = new ListConstructor(ORDERED_LIST_CONSTRUCTOR, sourceKeyExprList);
+        LiteralExpr sourceKeyFieldNameExpr = new LiteralExpr(new StringLiteral(SOURCE_KEY_IDENTIFIER.getValue()));
+        detailRecord.getFbList().add(new FieldBinding(sourceKeyFieldNameExpr, sourceKeyFieldValueExpr));
+
+        // Insert our dest-key into our detail record.
+        List<List<String>> edgeDestKey = elementLookupTable.getEdgeDestKey(edgeIdentifier);
+        List<Expression> destKeyExprList = LowerRewritingUtil.buildAccessorList(sourceExpr, edgeDestKey).stream()
+                .map(e -> (Expression) e).collect(Collectors.toList());
+        ListConstructor destKeyFieldValueExpr = new ListConstructor(ORDERED_LIST_CONSTRUCTOR, destKeyExprList);
+        LiteralExpr destKeyFieldNameExpr = new LiteralExpr(new StringLiteral(DEST_KEY_IDENTIFIER.getValue()));
+        detailRecord.getFbList().add(new FieldBinding(destKeyFieldNameExpr, destKeyFieldValueExpr));
+    }
+}
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
new file mode 100644
index 0000000..4a9a216
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeDirectionPrepare.java
@@ -0,0 +1,46 @@
+/*
+ * 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.function.prepare;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+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.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class EdgeDirectionPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("EdgeDirection");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr) {
+        if (!(inputExpr instanceof EdgePatternExpr)) {
+            return;
+        }
+        EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        EdgeDescriptor.EdgeDirection edgeDirection = edgeDescriptor.getEdgeDirection();
+        LiteralExpr fieldValueExpr = new LiteralExpr(new StringLiteral(edgeDirection.toString()));
+        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
new file mode 100644
index 0000000..5cc7bba
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/EdgeSourceVertexPrepare.java
@@ -0,0 +1,50 @@
+/*
+ * 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.function.prepare;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+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;
+
+public class EdgeSourceVertexPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("EdgeSourceVertex");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr) {
+        if (!(inputExpr instanceof EdgePatternExpr)) {
+            return;
+        }
+        EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        EdgeDescriptor.EdgeDirection edgeDirection = edgeDescriptor.getEdgeDirection();
+        VertexPatternExpr sourceVertexExpr = (edgeDirection == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT)
+                ? edgePatternExpr.getLeftVertex() : edgePatternExpr.getRightVertex();
+        VariableExpr sourceVariableExpr = new VariableExpr(sourceVertexExpr.getVariableExpr().getVar());
+        LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
+        FieldBinding fieldBinding = new FieldBinding(fieldNameExpr, sourceVariableExpr);
+        schemaRecord.getFbList().add(fieldBinding);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/ElementLabelPrepare.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/ElementLabelPrepare.java
new file mode 100644
index 0000000..bbe9bdc
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/ElementLabelPrepare.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.function.prepare;
+
+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.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+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.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class ElementLabelPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("ElementLabel");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr)
+            throws CompilationException {
+        LiteralExpr fieldValueExpr;
+        if (inputExpr instanceof VertexPatternExpr) {
+            VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) inputExpr;
+            ElementLabel elementLabel = vertexPatternExpr.getLabels().iterator().next();
+            fieldValueExpr = new LiteralExpr(new StringLiteral(elementLabel.toString()));
+
+        } else if (inputExpr instanceof EdgePatternExpr) {
+            EdgePatternExpr edgePatternExpr = (EdgePatternExpr) inputExpr;
+            EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+            ElementLabel elementLabel = edgeDescriptor.getEdgeLabels().iterator().next();
+            fieldValueExpr = new LiteralExpr(new StringLiteral(elementLabel.toString()));
+
+        } else {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Given illegal input expression!");
+        }
+        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/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
similarity index 65%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
index 25efe98..6c4c2b1 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/IFunctionPrepare.java
@@ -16,14 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+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.lang.common.base.Expression;
 
-/**
- * @see ExprAssembler
- */
 @FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public interface IFunctionPrepare {
+    Expression prepare(Expression sourceExpr, Expression inputExpr, GraphIdentifier graphIdentifier,
+            ElementLookupTable elementLookupTable) throws CompilationException;
 }
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
new file mode 100644
index 0000000..c6cde11
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/prepare/VertexDetailPrepare.java
@@ -0,0 +1,70 @@
+/*
+ * 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.function.prepare;
+
+import static org.apache.asterix.lang.common.expression.ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR;
+
+import java.util.ArrayList;
+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.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.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;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class VertexDetailPrepare extends AbstractElementPrepare {
+    public static final Identifier IDENTIFIER = new Identifier("VertexDetail");
+    public static final Identifier KEY_IDENTIFIER = new Identifier("VertexKey");
+
+    @Override
+    protected void transformRecord(RecordConstructor schemaRecord, Expression inputExpr, Expression sourceExpr)
+            throws CompilationException {
+        if (!(inputExpr instanceof VertexPatternExpr)) {
+            return;
+        }
+        VertexPatternExpr vertexPatternExpr = (VertexPatternExpr) inputExpr;
+
+        // Insert our detail record into our schema.
+        RecordConstructor detailRecord = new RecordConstructor(new ArrayList<>());
+        LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(IDENTIFIER.getValue()));
+        FieldBinding detailRecordBinding = new FieldBinding(fieldNameExpr, detailRecord);
+        schemaRecord.getFbList().add(detailRecordBinding);
+
+        // Insert our element-label into our detail record.
+        ElementLabelPrepare elementLabelPrepare = new ElementLabelPrepare();
+        elementLabelPrepare.transformRecord(detailRecord, inputExpr, sourceExpr);
+
+        // Insert our vertex-key into our detail record.
+        GraphElementIdentifier 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());
+        ListConstructor keyFieldValueExpr = new ListConstructor(ORDERED_LIST_CONSTRUCTOR, vertexKeyExprList);
+        LiteralExpr keyFieldNameExpr = new LiteralExpr(new StringLiteral(KEY_IDENTIFIER.getValue()));
+        detailRecord.getFbList().add(new FieldBinding(keyFieldNameExpr, keyFieldValueExpr));
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeIfFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeIfFunctionRewrite.java
deleted file mode 100644
index 319ec1b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeIfFunctionRewrite.java
+++ /dev/null
@@ -1,77 +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.function.rewrite;
-
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.record.EdgeRecord;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.literal.StringLiteral;
-import org.apache.asterix.lang.common.literal.TrueLiteral;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.sqlpp.expression.CaseExpression;
-
-/**
- * Given the expression EDGE*_IF(myEdgeVar, expr1, expr2), rewrite this function to return either expression (depending
- * on our owner).
- * 1. Access our edge direction.
- * 2. Build two conditions: one where our edge direction is equal to LEFT_TO_RIGHT, and another where our edge direction
- * is equal to RIGHT_TO_LEFT.
- * 3. Build our case statement.
- */
-public class EdgeIfFunctionRewrite implements IFunctionRewrite {
-    private final boolean isSourceVertex;
-
-    public EdgeIfFunctionRewrite(boolean isSourceRewrite) {
-        this.isSourceVertex = isSourceRewrite;
-    }
-
-    @Override
-    public Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException {
-        // Fetch our edge direction.
-        final Identifier detailSuffix = new Identifier(EdgeRecord.EDGE_DETAIL_NAME);
-        final Identifier directionSuffix = new Identifier(EdgeRecord.DIRECTION_FIELD_NAME);
-        FieldAccessor edgeDetailAccess = new FieldAccessor(callArguments.get(0), detailSuffix);
-        FieldAccessor edgeDirAccess = new FieldAccessor(edgeDetailAccess, directionSuffix);
-
-        // Create a LEFT_TO_RIGHT condition.
-        LiteralExpr l2RLiteral = new LiteralExpr(new StringLiteral(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT.name()));
-        List<Expression> l2ROperands = List.of(edgeDirAccess, l2RLiteral);
-        OperatorExpr l2RCondition = new OperatorExpr(l2ROperands, List.of(OperatorType.EQ), false);
-
-        // Create a RIGHT_TO_LEFT condition.
-        LiteralExpr r2LLiteral = new LiteralExpr(new StringLiteral(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT.name()));
-        List<Expression> r2LOperands = List.of(edgeDirAccess, r2LLiteral);
-        OperatorExpr r2LCondition = new OperatorExpr(r2LOperands, List.of(OperatorType.EQ), false);
-
-        // Build our CASE expression.
-        List<Expression> thenExpressionList = isSourceVertex ? List.of(callArguments.get(1), callArguments.get(2))
-                : List.of(callArguments.get(2), callArguments.get(1));
-        return new CaseExpression(new LiteralExpr(TrueLiteral.INSTANCE), List.of(l2RCondition, r2LCondition),
-                thenExpressionList, null);
-    }
-}
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 7288aa7..54b1963 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
@@ -18,18 +18,16 @@
  */
 package org.apache.asterix.graphix.function.rewrite;
 
-import java.util.List;
-
 import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.function.FunctionRewriteMap;
+import org.apache.asterix.graphix.function.GraphixFunctionMap;
 import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
 
 /**
- * @see FunctionRewriteMap
+ * @see GraphixFunctionMap
  */
 @FunctionalInterface
 public interface IFunctionRewrite {
-    Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException;
+    Expression apply(GraphixRewritingContext graphixRewritingContext, CallExpr callExpr) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesFunctionRewrite.java
deleted file mode 100644
index b961000..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesFunctionRewrite.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.function.rewrite;
-
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Expression;
-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.VariableExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Given the expression PATH_EDGES(myPathVar), rewrite this function to extract all edges from the given path.
- * 1. Create a field access to get the edge of our path record.
- * 2. Filter out unknown edges (may occur w/ dangling vertices in paths).
- * 2. Build up a SELECT-EXPR whose FROM-TERM is our path variable.
- */
-public class PathEdgesFunctionRewrite implements IFunctionRewrite {
-    @Override
-    public Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException {
-        // Build the SELECT-EXPR. We want to get the edge from our path record.
-        VariableExpr edgeVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        FromTerm fromTerm = new FromTerm(callArguments.get(0), edgeVar, null, null);
-        FunctionSignature isUnknownFunctionSignature = new FunctionSignature(BuiltinFunctions.IS_UNKNOWN);
-        FunctionSignature notFunctionSignature = new FunctionSignature(BuiltinFunctions.NOT);
-        FieldAccessor edgeAccess = new FieldAccessor(edgeVar, new Identifier(PathRecord.EDGE_FIELD_NAME));
-        List<AbstractClause> whereClauses = List.of(new WhereClause(new CallExpr(notFunctionSignature,
-                List.of(new CallExpr(isUnknownFunctionSignature, List.of(edgeAccess))))));
-        return LowerRewritingUtil.buildSelectWithFromAndElement(fromTerm, whereClauses, edgeAccess);
-    }
-}
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
new file mode 100644
index 0000000..3a1cca0
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesRewrite.java
@@ -0,0 +1,44 @@
+/*
+ * 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.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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class PathEdgesRewrite implements IFunctionRewrite {
+    @Override
+    public Expression apply(GraphixRewritingContext graphixRewritingContext, CallExpr callExpr)
+            throws CompilationException {
+        if (callExpr.getExprList().size() != 1) {
+            throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
+                    GraphixFunctionIdentifiers.PATH_EDGES.toString());
+        }
+        Identifier pathEdgeIdentifier = new Identifier(PathPatternAction.PATH_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/PathHopCountFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountFunctionRewrite.java
deleted file mode 100644
index 9b1801f..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountFunctionRewrite.java
+++ /dev/null
@@ -1,66 +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.function.rewrite;
-
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.lang.common.base.Expression;
-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.LiteralExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.IntegerLiteral;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Given the expression PATH_HOP_COUNT(myPathVar), rewrite this function to return a count of edges in the given path.
- * 1. First, create a field access to get the edge of our path record.
- * 2. Next, filter out all unknown edges of our path. An path-edge can be unknown if a dangling vertex exists.
- * 3. Build up a SELECT-EXPR whose FROM-TERM is our path variable.
- * 4. Finally, return the LENGTH of the SELECT-EXPR list.
- */
-public class PathHopCountFunctionRewrite implements IFunctionRewrite {
-    @Override
-    public Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException {
-        // We only want edges that are not unknown.
-        VariableExpr edgeVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        FromTerm fromTerm = new FromTerm(callArguments.get(0), edgeVar, null, null);
-        FieldAccessor edgeAccess = new FieldAccessor(edgeVar, new Identifier(PathRecord.EDGE_FIELD_NAME));
-        FunctionSignature isUnknownFunctionSignature = new FunctionSignature(BuiltinFunctions.IS_UNKNOWN);
-        FunctionSignature notFunctionSignature = new FunctionSignature(BuiltinFunctions.NOT);
-        CallExpr callExpr = new CallExpr(notFunctionSignature,
-                List.of(new CallExpr(isUnknownFunctionSignature, List.of(edgeAccess))));
-        SelectExpression selectExpression = LowerRewritingUtil.buildSelectWithFromAndElement(fromTerm,
-                List.of(new WhereClause(callExpr)), new LiteralExpr(new IntegerLiteral(1)));
-
-        // Our SELECT returns a list. Count the length of this list.
-        FunctionSignature lengthFunctionSignature = new FunctionSignature(BuiltinFunctions.LEN);
-        return new CallExpr(lengthFunctionSignature, List.of(selectExpression));
-    }
-}
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
new file mode 100644
index 0000000..32ebd92
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountRewrite.java
@@ -0,0 +1,58 @@
+/*
+ * 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.function.rewrite;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+
+public class PathHopCountRewrite implements IFunctionRewrite {
+    @Override
+    public Expression apply(GraphixRewritingContext graphixRewritingContext, CallExpr callExpr)
+            throws CompilationException {
+        if (callExpr.getExprList().size() != 1) {
+            throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
+                    GraphixFunctionIdentifiers.PATH_HOP_COUNT.toString());
+        }
+
+        // Access the edges in our path.
+        List<Expression> countFunctionArguments = new ArrayList<>();
+        Identifier pathEdgeIdentifier = new Identifier(PathPatternAction.PATH_EDGES_FIELD_NAME);
+        FieldAccessor pathEdgeAccess = new FieldAccessor(callExpr.getExprList().get(0), pathEdgeIdentifier);
+        pathEdgeAccess.setSourceLocation(callExpr.getSourceLocation());
+        countFunctionArguments.add(pathEdgeAccess);
+
+        // Count the number of edges we have.
+        FunctionSignature countFunctionSignature = new FunctionSignature(BuiltinFunctions.LEN);
+        CallExpr countCallExpr = new CallExpr(countFunctionSignature, countFunctionArguments);
+        countCallExpr.setSourceLocation(callExpr.getSourceLocation());
+        return countCallExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathLabelsFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathLabelsFunctionRewrite.java
deleted file mode 100644
index f998ce3..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathLabelsFunctionRewrite.java
+++ /dev/null
@@ -1,79 +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.function.rewrite;
-
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.record.IElementRecord;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.ListConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.UnnestType;
-
-/**
- * Given the expression PATH_LABELS(myPathVar), rewrite this function to extract unique labels from the given path.
- * 1. First, we create a FROM-TERM from the given call argument. We assume that this is a path variable.
- * 2. Next, we build an ordered-list expression of LABEL accesses to the left vertex, edge, and right vertex of our
- * path record.
- * 3. UNNEST this ordered-list expression to get the label values, and mark the associated SELECT-EXPR as DISTINCT to
- * remove duplicates labels. Building this ordered-list and then UNNESTing said list is required to get a flat list of
- * label values.
- *
- * TODO (GLENN): We can definitely remove the "DISTINCT" and "UNNEST", but this is logically equivalent.
- */
-public class PathLabelsFunctionRewrite implements IFunctionRewrite {
-    @Override
-    public Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException {
-        // Access the label field in each of edge record fields.
-        VariableExpr edgeVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        Function<String, FieldAccessor> labelAccessBuilder = i -> {
-            final Identifier detailSuffix = new Identifier(IElementRecord.ELEMENT_DETAIL_NAME);
-            final Identifier labelSuffix = new Identifier(IElementRecord.ELEMENT_LABEL_FIELD_NAME);
-            FieldAccessor elementAccess = new FieldAccessor(edgeVar, new Identifier(i));
-            FieldAccessor detailAccess = new FieldAccessor(elementAccess, detailSuffix);
-            return new FieldAccessor(detailAccess, labelSuffix);
-        };
-        FieldAccessor leftVertexLabel = labelAccessBuilder.apply(PathRecord.LEFT_VERTEX_FIELD_NAME);
-        FieldAccessor edgeLabel = labelAccessBuilder.apply(PathRecord.EDGE_FIELD_NAME);
-        FieldAccessor rightVertexLabel = labelAccessBuilder.apply(PathRecord.RIGHT_VERTEX_FIELD_NAME);
-
-        // Build a list of labels, and UNNEST this list to get a flat list of labels.
-        ListConstructor listConstructor = new ListConstructor(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR,
-                List.of(leftVertexLabel, edgeLabel, rightVertexLabel));
-        VariableExpr labelVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        UnnestClause edgeUnnest = new UnnestClause(UnnestType.INNER, listConstructor, labelVar, null, null);
-        FromTerm fromTerm = new FromTerm(callArguments.get(0), edgeVar, null, List.of(edgeUnnest));
-
-        // Build the SELECT-EXPR. We will also mark this SELECT-CLAUSE as DISTINCT.
-        SelectExpression selectExpr = LowerRewritingUtil.buildSelectWithFromAndElement(fromTerm, null, labelVar);
-        selectExpr.getSelectSetOperation().getLeftInput().getSelectBlock().getSelectClause().setDistinct(true);
-        return selectExpr;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesFunctionRewrite.java
deleted file mode 100644
index 4ffb338..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesFunctionRewrite.java
+++ /dev/null
@@ -1,80 +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.function.rewrite;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSelectWithFromAndElement;
-
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Expression;
-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.ListConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.UnnestType;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Given the expression PATH_VERTICES(myPathVar), rewrite this function to extract all vertices from the given path.
- * 1. First, we create a FROM-TERM from the given call argument. We assume that this is a path variable.
- * 2. Next, we build an ordered-list expression of accesses to the left vertex and the right vertex of our path record.
- * 3. UNNEST this ordered-list expression to get the vertex values, and mark the associated SELECT-EXPR as DISTINCT to
- * remove duplicates vertices. Building this ordered-list and then UNNESTing said list is required to get a flat list of
- * vertices.
- *
- * TODO (GLENN): We can definitely remove the "DISTINCT" and "UNNEST", but this is logically equivalent.
- */
-public class PathVerticesFunctionRewrite implements IFunctionRewrite {
-    @Override
-    public Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
-            throws CompilationException {
-        // Access the left and right vertices of our edge record.
-        VariableExpr edgeVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        FieldAccessor leftVertex = new FieldAccessor(edgeVar, new Identifier(PathRecord.LEFT_VERTEX_FIELD_NAME));
-        FieldAccessor rightVertex = new FieldAccessor(edgeVar, new Identifier(PathRecord.RIGHT_VERTEX_FIELD_NAME));
-
-        // Build a list of vertices, and UNNEST this list to get a flat list of vertices.
-        ListConstructor listConstructor =
-                new ListConstructor(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR, List.of(leftVertex, rightVertex));
-        VariableExpr vertexVar = new VariableExpr(graphixRewritingContext.getNewVariable());
-        UnnestClause edgeUnnest = new UnnestClause(UnnestType.INNER, listConstructor, vertexVar, null, null);
-
-        // Ensure that we filter out unknown vertices (can occur with dangling vertices).
-        FunctionSignature isUnknownFunctionSignature = new FunctionSignature(BuiltinFunctions.IS_UNKNOWN);
-        FunctionSignature notFunctionSignature = new FunctionSignature(BuiltinFunctions.NOT);
-        List<AbstractClause> whereClauses = List.of(new WhereClause(new CallExpr(notFunctionSignature,
-                List.of(new CallExpr(isUnknownFunctionSignature, List.of(vertexVar))))));
-        FromTerm fromTerm = new FromTerm(callArguments.get(0), edgeVar, null, List.of(edgeUnnest));
-
-        // Build the SELECT-EXPR. We will also mark this SELECT-CLAUSE as DISTINCT.
-        SelectExpression selectExpr = buildSelectWithFromAndElement(fromTerm, whereClauses, vertexVar);
-        selectExpr.getSelectSetOperation().getLeftInput().getSelectBlock().getSelectClause().setDistinct(true);
-        return selectExpr;
-    }
-}
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
new file mode 100644
index 0000000..1dbc027
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesRewrite.java
@@ -0,0 +1,44 @@
+/*
+ * 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.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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+public class PathVerticesRewrite implements IFunctionRewrite {
+    @Override
+    public Expression apply(GraphixRewritingContext graphixRewritingContext, CallExpr callExpr)
+            throws CompilationException {
+        if (callExpr.getExprList().size() != 1) {
+            throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
+                    GraphixFunctionIdentifiers.PATH_VERTICES.toString());
+        }
+        Identifier pathVertexIdentifier = new Identifier(PathPatternAction.PATH_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
new file mode 100644
index 0000000..73fb2a6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/SchemaAccessRewrite.java
@@ -0,0 +1,60 @@
+/*
+ * 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.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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+/**
+ * For most function rewrites, we just need to access the "_GraphixSchemaIdentifier" field we attached during schema
+ * enrichment (i.e. on "prepare").
+ */
+public class SchemaAccessRewrite implements IFunctionRewrite {
+    protected final FunctionIdentifier workingFunctionIdentifier;
+    protected final Identifier workingFunctionPrepareIdentifier;
+
+    public SchemaAccessRewrite(FunctionIdentifier functionIdentifier, Identifier functionPrepareIdentifier) {
+        this.workingFunctionIdentifier = functionIdentifier;
+        this.workingFunctionPrepareIdentifier = functionPrepareIdentifier;
+    }
+
+    @Override
+    public Expression apply(GraphixRewritingContext graphixRewritingContext, CallExpr callExpr)
+            throws CompilationException {
+        if (callExpr.getExprList().size() != 1) {
+            throw new CompilationException(ErrorCode.ILLEGAL_FUNCTION_USE, callExpr.getSourceLocation(),
+                    workingFunctionIdentifier.toString());
+        }
+
+        // Access our schema, then our element-label field.
+        Identifier schemaIdentifier = AbstractElementPrepare.GRAPHIX_SCHEMA_IDENTIFIER;
+        FieldAccessor schemaFieldAccessor = new FieldAccessor(callExpr.getExprList().get(0), schemaIdentifier);
+        schemaFieldAccessor.setSourceLocation(callExpr.getSourceLocation());
+        FieldAccessor finalFieldAccessor = new FieldAccessor(schemaFieldAccessor, workingFunctionPrepareIdentifier);
+        finalFieldAccessor.setSourceLocation(callExpr.getSourceLocation());
+        return finalFieldAccessor;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/GraphixSchemaAnnotation.java
similarity index 63%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/GraphixSchemaAnnotation.java
index 25efe98..6519615 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/annotation/GraphixSchemaAnnotation.java
@@ -16,14 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+package org.apache.asterix.graphix.lang.annotation;
 
-import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.hyracks.algebricks.core.algebra.expressions.IExpressionAnnotation;
 
 /**
- * @see ExprAssembler
+ * Annotation used to indicate that a CALL-EXPR was used to decorate an expression w/ Graphix schema information.
  */
-@FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public class GraphixSchemaAnnotation implements IExpressionAnnotation {
+    public static final GraphixSchemaAnnotation INSTANCE = new GraphixSchemaAnnotation();
+
+    private GraphixSchemaAnnotation() {
+    }
 }
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
new file mode 100644
index 0000000..1a80aff
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrLetClause.java
@@ -0,0 +1,64 @@
+/*
+ * 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
new file mode 100644
index 0000000..98ad42a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/CorrWhereClause.java
@@ -0,0 +1,57 @@
+/*
+ * 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 0e93470..d8d4cad 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
@@ -107,7 +107,8 @@
 
     @Override
     public String toString() {
-        return (graphConstructor == null) ? dataverse.toString() + "." + name : graphConstructor.toString();
+        return (graphConstructor != null) ? graphConstructor.toString()
+                : ((dataverse == null) ? name.getValue() : (dataverse + "." + name));
     }
 
     @Override
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
index 693ee70..8871aaf 100644
--- 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
@@ -59,11 +59,11 @@
 
     @Override
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
-        if (hasFromGraphClause()) {
-            return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+        if (hasFromClause()) {
+            return ((ISqlppVisitor<R, T>) visitor).visit(this, arg);
 
         } else {
-            return ((ISqlppVisitor<R, T>) visitor).visit(this, arg);
+            return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
         }
     }
 
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 e0fbf7f..11580e0 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
@@ -35,7 +35,7 @@
  * 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.assembly.IsomorphismLowerAssembly} for more detail.
+ * {@link org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction} for more detail.
  */
 public class MatchClause extends AbstractClause {
     private final List<PathPatternExpr> pathExpressions;
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 718563a..666141b 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
@@ -36,9 +36,9 @@
  */
 public class EdgePatternExpr extends AbstractExpression {
     private final List<VertexPatternExpr> internalVertices;
-    private final VertexPatternExpr leftVertex;
-    private final VertexPatternExpr rightVertex;
     private final EdgeDescriptor edgeDescriptor;
+    private VertexPatternExpr leftVertex;
+    private VertexPatternExpr rightVertex;
 
     public EdgePatternExpr(VertexPatternExpr leftVertex, VertexPatternExpr rightVertex, EdgeDescriptor edgeDescriptor) {
         this.leftVertex = Objects.requireNonNull(leftVertex);
@@ -70,12 +70,39 @@
         return internalVertices;
     }
 
+    public void setLeftVertex(VertexPatternExpr leftVertex) {
+        this.leftVertex = leftVertex;
+    }
+
+    public void setRightVertex(VertexPatternExpr rightVertex) {
+        this.rightVertex = rightVertex;
+    }
+
     public void replaceInternalVertices(List<VertexPatternExpr> internalVertices) {
         this.internalVertices.clear();
         this.internalVertices.addAll(internalVertices);
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(leftVertex, rightVertex, edgeDescriptor, internalVertices);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof EdgePatternExpr)) {
+            return false;
+        }
+        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);
+    }
+
+    @Override
     public String toString() {
         return leftVertex.toString() + edgeDescriptor.toString() + rightVertex.toString();
     }
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 c0e7c36..245be24 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
@@ -89,9 +89,8 @@
     /**
      * 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 label that groups the aforementioned body with other vertices of the same label.
-     * - A list of primary key fields, which must be the same as other vertices of the same label. These fields are
-     * used in the JOIN clause with edges.
+     * - A single vertex label that uniquely identifies the vertex.
+     * - A list of primary key fields, used in the JOIN clause with edges.
      */
     public static class VertexConstructor extends AbstractLangExpression {
         private final List<Integer> primaryKeySourceIndicators;
@@ -162,13 +161,11 @@
     /**
      * 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 groups the aforementioned body with other edges of the same label.
+     * - 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, which must be the same as other edges of the same label. These fields are used in
-     * the JOIN clause with the corresponding source vertices.
-     * - A list of destination key fields, which must be the same as other edges of the same label. These fields are
-     * used in the JOIN clause with the corresponding destination vertices.
+     * - 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.
      */
     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 04efc0d..1057731 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
@@ -18,20 +18,24 @@
  */
 package org.apache.asterix.graphix.lang.expression;
 
+import java.util.ArrayList;
 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.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.
+ * 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).
  */
 public class PathPatternExpr extends AbstractExpression {
+    private final List<LetClause> reboundSubPathExpressions;
     private final List<VertexPatternExpr> vertexExpressions;
     private final List<EdgePatternExpr> edgeExpressions;
     private VariableExpr variableExpr;
@@ -41,6 +45,9 @@
         this.vertexExpressions = Objects.requireNonNull(vertexExpressions);
         this.edgeExpressions = Objects.requireNonNull(edgeExpressions);
         this.variableExpr = variableExpr;
+
+        // We will build this list on canonicalization.
+        this.reboundSubPathExpressions = new ArrayList<>();
     }
 
     public List<VertexPatternExpr> getVertexExpressions() {
@@ -55,6 +62,10 @@
         return variableExpr;
     }
 
+    public List<LetClause> getReboundSubPathList() {
+        return reboundSubPathExpressions;
+    }
+
     public void setVariableExpr(VariableExpr variableExpr) {
         this.variableExpr = variableExpr;
     }
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 39a4da6..54cbfa4 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
@@ -19,6 +19,7 @@
 package org.apache.asterix.graphix.lang.expression;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -63,6 +64,23 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(labels, variableExpr);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof VertexPatternExpr)) {
+            return false;
+        }
+        VertexPatternExpr that = (VertexPatternExpr) object;
+        return Objects.equals(this.labels, that.labels) && Objects.equals(this.variableExpr, that.variableExpr);
+    }
+
+    @Override
     public String toString() {
         String labelsString = labels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
         String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphElementBodyParser.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphElementBodyParser.java
index c4634e0..2b21bb6 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphElementBodyParser.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/parser/GraphElementBodyParser.java
@@ -22,40 +22,28 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.graphix.metadata.entity.schema.Element;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.metadata.entity.schema.IElement;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public final class GraphElementBodyParser {
     // Just a wrapper for the parseGraphElementBody method.
-    public static GraphElementDecl parse(Element element, GraphixParserFactory parserFactory,
+    public static GraphElementDeclaration parse(IElement element, GraphixParserFactory parserFactory,
             IWarningCollector warningCollector) throws CompilationException {
-        GraphElementDecl graphElementDecl = null;
-        for (String definition : element.getDefinitionBodies()) {
-            // Parse our the definition.
-            GraphixParser parser = (GraphixParser) parserFactory.createParser(new StringReader(definition));
-            GraphElementDecl parsedElementDecl;
-            try {
-                parsedElementDecl = parser.parseGraphElementBody(element.getIdentifier());
+        String definitionBody = element.getDefinitionBody();
+        GraphixParser parser = (GraphixParser) parserFactory.createParser(new StringReader(definitionBody));
+        GraphElementDeclaration parsedElementDecl;
+        try {
+            parsedElementDecl = parser.parseGraphElementBody(element.getIdentifier());
 
-            } catch (CompilationException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR,
-                        "Bad definition for a graph element: " + e.getMessage());
-            }
-
-            // Accumulate the element bodies.
-            if (graphElementDecl == null) {
-                graphElementDecl = parsedElementDecl;
-
-            } else {
-                graphElementDecl.getBodies().add(parsedElementDecl.getBodies().get(0));
-            }
-
-            // Gather any warnings.
-            if (warningCollector != null) {
-                parser.getWarnings(warningCollector);
-            }
+        } catch (CompilationException e) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR,
+                    "Bad definition for a graph element: " + e.getMessage());
         }
-        return graphElementDecl;
+
+        if (warningCollector != null) {
+            parser.getWarnings(warningCollector);
+        }
+        return parsedElementDecl;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java
index 1311244..86fc0ce 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixQueryRewriter.java
@@ -22,27 +22,28 @@
 import java.io.StringWriter;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
 import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
 import org.apache.asterix.graphix.lang.rewrites.print.SqlppASTPrintQueryVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementAnalysisVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.CanonicalExpansionVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.ElementLookupTableVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementResolutionVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.GenerateVariableVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.FunctionResolutionVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixFunctionCallVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.GroupByAggSugarVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.PopulateUnknownsVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.PostRewriteCheckVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.PostRewriteVariableVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.PreRewriteCheckVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.ScopingCheckVisitor;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.rewrites.visitor.StructureAnalysisVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.StructureResolutionVisitor;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IReturningStatement;
@@ -63,14 +64,13 @@
  * 1. Perform an error-checking on the fresh AST (immediately after parsing).
  * 2. Populate the unknowns in our AST (e.g. vertex / edge variables, projections, GROUP-BY keys).
  * 3. Perform a variable-scoping pass to identify illegal variables (either duplicate or out-of-scope).
- * 4. Resolve our Graphix function calls
- * 4. For the remainder of our Graphix function calls, rewrite each call into a subtree w/o Graphix functions.
+ * 4. Resolve all of our function calls (Graphix, SQL++, and user-defined).
  * 5. Perform resolution of unlabeled vertices / edges, as well as edge directions.
  * 6. Using the labels of the vertices / edges in our AST, fetch the relevant graph elements from our metadata.
- * 7. Perform an analysis pass to find a) all vertices that are not attached to an edge (i.e. dangling vertices), b)
- * a list of edges, ordered by their dependencies on other edges, and c) a list of optional vertices (from LEFT-MATCH
- * clauses).
- * 8. Perform Graphix AST node lowering to SQL++ AST nodes.
+ * 7. Perform a canonical Graphix lowering pass to remove ambiguities (e.g. undirected edges).
+ * 8. Perform a lowering pass to transform Graphix AST nodes to SQL++ AST nodes.
+ * 9. Perform another lowering pass to transform Graphix CALL-EXPR nodes to SQL++ AST nodes.
+ * 10. Perform all SQL++ rewrites on our newly lowered AST.
  */
 public class GraphixQueryRewriter extends SqlppQueryRewriter {
     private static final Logger LOGGER = LogManager.getLogger(GraphixQueryRewriter.class);
@@ -86,30 +86,31 @@
 
     @Override
     public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
-            boolean allowNonStoredUdfCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+            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-rewrite check (user query validation).");
-        PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(langRewritingContext);
+        LOGGER.trace("Performing pre-Graphix-rewrite check (user query validation).");
+        GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
+        PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(graphixRewritingContext);
         topStatement.getBody().accept(preRewriteCheckVisitor, null);
 
         // Perform the Graphix rewrites.
-        rewriteGraphixASTNodes(langRewritingContext, topStatement);
+        rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
 
         // Sanity check: ensure that no graph AST nodes exist after this point.
-        LOGGER.trace("Performing post-rewrite check (making sure no graph AST nodes exist).");
+        LOGGER.trace("Performing post-Graphix-rewrite check (making sure no graph AST nodes exist).");
         PostRewriteCheckVisitor postRewriteCheckVisitor = new PostRewriteCheckVisitor();
         topStatement.getBody().accept(postRewriteCheckVisitor, null);
 
         // Perform the remainder of the SQL++ rewrites.
         LOGGER.debug("Ending Graphix AST rewrites. Now starting SQL++ AST rewrites.");
-        super.rewrite(langRewritingContext, topStatement, allowNonStoredUdfCalls, inlineUdfsAndViews, externalVars);
-        LOGGER.debug("Ending SQL++ AST rewrites.");
+        rewriteSQLPPASTNodes(langRewritingContext, topStatement, allowNonStoredUDFCalls, inlineUdfsAndViews,
+                externalVars);
 
         // If desired, log the SQL++ query equivalent (i.e. turn the AST back into a query and log this).
-        MetadataProvider metadataProvider = langRewritingContext.getMetadataProvider();
+        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
         String printRewriteMetadataKeyName = GraphixCompilationProvider.PRINT_REWRITE_METADATA_CONFIG;
         String printRewriteOption = (String) metadataProvider.getConfig().get(printRewriteMetadataKeyName);
         if ((printRewriteOption != null && printRewriteOption.equalsIgnoreCase("true")) || LOGGER.isTraceEnabled()) {
@@ -118,98 +119,137 @@
             new SqlppASTPrintQueryVisitor(printWriter).visit((Query) topStatement, null);
             LOGGER.log(LOGGER.getLevel(), "Rewritten Graphix query: " + stringWriter);
         }
+
+        // Update the variable counter on our context.
+        topStatement.setVarCounter(graphixRewritingContext.getVarCounter().get());
+        LOGGER.debug("Ending SQL++ AST rewrites.");
     }
 
     public void loadNormalizedGraphElement(GraphixRewritingContext graphixRewritingContext,
-            GraphElementDecl graphElementDecl) throws CompilationException {
-        List<Expression> normalizedBodies = graphElementDecl.getNormalizedBodies();
-        if (normalizedBodies.size() != graphElementDecl.getBodies().size()) {
-            for (Expression body : graphElementDecl.getBodies()) {
-                Dataverse defaultDataverse = graphixRewritingContext.getMetadataProvider().getDefaultDataverse();
-                Dataverse targetDataverse;
+            GraphElementDeclaration graphElementDeclaration) throws CompilationException {
+        if (graphElementDeclaration.getNormalizedBody() == null) {
+            Dataverse defaultDataverse = graphixRewritingContext.getMetadataProvider().getDefaultDataverse();
+            Dataverse targetDataverse;
 
-                // We might need to change our dataverse, if the element definition requires a different one.
-                DataverseName elementName = graphElementDecl.getIdentifier().getGraphIdentifier().getDataverseName();
-                if (elementName.equals(defaultDataverse.getDataverseName())) {
-                    targetDataverse = defaultDataverse;
+            // We might need to change our dataverse, if the element definition requires a different one.
+            DataverseName elementName = graphElementDeclaration.getIdentifier().getGraphIdentifier().getDataverseName();
+            if (elementName.equals(defaultDataverse.getDataverseName())) {
+                targetDataverse = defaultDataverse;
 
-                } else {
-                    try {
-                        targetDataverse = graphixRewritingContext.getMetadataProvider().findDataverse(elementName);
-
-                    } catch (AlgebricksException e) {
-                        throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e,
-                                graphElementDecl.getSourceLocation(), elementName);
-                    }
-                }
-                graphixRewritingContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
-
-                // Get the body of the rewritten query.
+            } else {
                 try {
-                    Query wrappedQuery = ExpressionUtils.createWrappedQuery(body, graphElementDecl.getSourceLocation());
-                    LangRewritingContext langRewritingContext = graphixRewritingContext.getLangRewritingContext();
-                    bodyRewriter.rewrite(langRewritingContext, wrappedQuery, false, false, List.of());
-                    normalizedBodies.add(wrappedQuery.getBody());
+                    targetDataverse = graphixRewritingContext.getMetadataProvider().findDataverse(elementName);
 
-                } catch (CompilationException e) {
-                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, body.getSourceLocation(),
-                            "Bad definition for a graph element: " + e.getMessage());
-
-                } finally {
-                    // Switch back to the working dataverse.
-                    graphixRewritingContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
+                } catch (AlgebricksException e) {
+                    throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e,
+                            graphElementDeclaration.getSourceLocation(), elementName);
                 }
             }
+            graphixRewritingContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
+
+            // Get the body of the rewritten query.
+            Expression rawBody = graphElementDeclaration.getRawBody();
+            try {
+                Query wrappedQuery =
+                        ExpressionUtils.createWrappedQuery(rawBody, graphElementDeclaration.getSourceLocation());
+                bodyRewriter.rewrite(graphixRewritingContext, wrappedQuery, false, false, List.of());
+                graphElementDeclaration.setNormalizedBody(wrappedQuery.getBody());
+
+            } catch (CompilationException e) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, rawBody.getSourceLocation(),
+                        "Bad definition for a graph element: " + e.getMessage());
+
+            } finally {
+                // Switch back to the working dataverse.
+                graphixRewritingContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
+            }
         }
     }
 
-    public void rewriteGraphixASTNodes(LangRewritingContext langRewritingContext, IReturningStatement topStatement)
-            throws CompilationException {
+    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("Generating names for unnamed elements (both graph and non-graph) in our AST.");
-        GraphixRewritingContext graphixRewritingContext = new GraphixRewritingContext(langRewritingContext);
-        GenerateVariableVisitor generateVariableVisitor = new GenerateVariableVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(generateVariableVisitor, null);
+        LOGGER.trace("Populating unknowns (both graph and non-graph) in our AST.");
+        PopulateUnknownsVisitor populateUnknownsVisitor = new PopulateUnknownsVisitor(graphixRewritingContext);
+        topStatement.getBody().accept(populateUnknownsVisitor, null);
 
         // Verify that variables are properly within scope.
         LOGGER.trace("Verifying that variables are unique and are properly scoped.");
-        ScopingCheckVisitor scopingCheckVisitor = new ScopingCheckVisitor(langRewritingContext);
+        ScopingCheckVisitor scopingCheckVisitor = new ScopingCheckVisitor(graphixRewritingContext);
         topStatement.getBody().accept(scopingCheckVisitor, null);
 
-        // Handle all Graphix-specific function calls to SQL++ rewrites.
-        LOGGER.trace("Rewriting all Graphix function calls to a SQL++ subtree or a function-less Graphix subtree.");
-        GraphixFunctionCallVisitor graphixFunctionCallVisitor = new GraphixFunctionCallVisitor(graphixRewritingContext);
-        topStatement.getBody().accept(graphixFunctionCallVisitor, null);
+        // Resolve all of our (Graphix, SQL++, and user-defined) function calls.
+        LOGGER.trace("Resolving Graphix, SQL++, and user-defined function calls.");
+        FunctionResolutionVisitor functionResolutionVisitor =
+                new FunctionResolutionVisitor(graphixRewritingContext, allowNonStoredUDFCalls);
+        topStatement.getBody().accept(functionResolutionVisitor, null);
 
         // Attempt to resolve our vertex labels, edge labels, and edge directions.
         LOGGER.trace("Performing label and edge direction resolution.");
-        ElementResolutionVisitor elementResolutionVisitor = new ElementResolutionVisitor(
-                graphixRewritingContext.getMetadataProvider(), graphixRewritingContext.getWarningCollector());
-        topStatement.getBody().accept(elementResolutionVisitor, null);
+        StructureResolutionVisitor structureResolutionVisitor = new StructureResolutionVisitor(graphixRewritingContext);
+        topStatement.getBody().accept(structureResolutionVisitor, null);
 
         // Fetch all relevant graph element declarations, using the element labels.
         LOGGER.trace("Fetching relevant edge and vertex bodies from our graph schema.");
-        ElementLookupTable<GraphElementIdentifier> elementLookupTable = new ElementLookupTable<>();
+        ElementLookupTable elementLookupTable = new ElementLookupTable();
         ElementLookupTableVisitor elementLookupTableVisitor =
-                new ElementLookupTableVisitor(elementLookupTable, graphixRewritingContext.getMetadataProvider(),
-                        parserFactory, graphixRewritingContext.getWarningCollector());
+                new ElementLookupTableVisitor(graphixRewritingContext, elementLookupTable, parserFactory);
         topStatement.getBody().accept(elementLookupTableVisitor, null);
-        for (GraphElementDecl graphElementDecl : elementLookupTable) {
-            loadNormalizedGraphElement(graphixRewritingContext, graphElementDecl);
+        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
+            loadNormalizedGraphElement(graphixRewritingContext, graphElementDeclaration);
         }
 
+        // Expand vertex and edge patterns to remove all ambiguities (into a canonical form).
+        LOGGER.trace("Expanding vertex and edge patterns to a canonical form.");
+        CanonicalExpansionVisitor canonicalExpansionVisitor = new CanonicalExpansionVisitor(graphixRewritingContext);
+        topStatement.setBody(topStatement.getBody().accept(canonicalExpansionVisitor, null));
+
         // Perform an analysis pass to get our edge dependencies, dangling vertices, and FROM-GRAPH-CLAUSE variables.
         LOGGER.trace("Collecting edge dependencies, dangling vertices, and FROM-GRAPH-CLAUSE specific variables.");
-        ElementAnalysisVisitor elementAnalysisVisitor = new ElementAnalysisVisitor();
-        topStatement.getBody().accept(elementAnalysisVisitor, null);
-        Map<FromGraphClause, ElementAnalysisVisitor.FromGraphClauseContext> fromGraphClauseContextMap =
-                elementAnalysisVisitor.getFromGraphClauseContextMap();
+        StructureAnalysisVisitor structureAnalysisVisitor = new StructureAnalysisVisitor(graphixRewritingContext);
+        topStatement.getBody().accept(structureAnalysisVisitor, null);
 
         // Transform all graph AST nodes (i.e. perform the representation lowering).
-        LOGGER.trace("Lowering the Graphix AST representation to a pure SQL++ representation.");
-        GraphixLoweringVisitor graphixLoweringVisitor =
-                new GraphixLoweringVisitor(fromGraphClauseContextMap, elementLookupTable, graphixRewritingContext);
-        topStatement.getBody().accept(graphixLoweringVisitor, null);
+        LOGGER.trace("Lowering the Graphix AST-specific nodes representation to a pure SQL++ representation.");
+        GraphixLoweringVisitor graphixLoweringVisitor = new GraphixLoweringVisitor(graphixRewritingContext,
+                elementLookupTable, structureAnalysisVisitor.getFromGraphClauseContextMap());
+        topStatement.setBody(topStatement.getBody().accept(graphixLoweringVisitor, null));
+
+        // Lower all of our Graphix function calls (and perform schema-enrichment).
+        LOGGER.trace("Lowering the Graphix CALL-EXPR nodes to a pure SQL++ representation.");
+        GraphixFunctionCallVisitor graphixFunctionCallVisitor = new GraphixFunctionCallVisitor(graphixRewritingContext);
+        topStatement.getBody().accept(graphixFunctionCallVisitor, null);
+    }
+
+    public void rewriteSQLPPASTNodes(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
+            boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+            throws CompilationException {
+        super.setup(langRewritingContext, topStatement, externalVars, allowNonStoredUDFCalls, inlineUdfsAndViews);
+        super.substituteGroupbyKeyExpression();
+        super.rewriteGroupBys();
+        super.inlineColumnAlias();
+        super.rewriteWindowExpressions();
+        super.rewriteGroupingSets();
+
+        // We need to override the default behavior of our variable check + rewrite visitor...
+        PostRewriteVariableVisitor postRewriteVariableVisitor = new PostRewriteVariableVisitor(langRewritingContext,
+                langRewritingContext.getMetadataProvider(), externalVars);
+        topStatement.accept(postRewriteVariableVisitor, null);
+        super.extractAggregatesFromCaseExpressions();
+
+        // ...and our GROUP-BY rewrite visitor.
+        GroupByAggSugarVisitor groupByAggSugarVisitor = new GroupByAggSugarVisitor(langRewritingContext, externalVars);
+        topStatement.accept(groupByAggSugarVisitor, null);
+
+        // The remainder of our rewrites are the same.
+        super.rewriteWindowAggregationSugar();
+        super.rewriteOperatorExpression();
+        super.rewriteCaseExpressions();
+        super.rewriteListInputFunctions();
+        super.rewriteRightJoins();
+        super.loadAndInlineUdfsAndViews();
+        super.rewriteSpecialFunctionNames();
+        super.inlineWithExpressions();
     }
 
     @Override
@@ -217,26 +257,30 @@
         return new SqlppFunctionBodyRewriter(parserFactory) {
             @Override
             public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
-                    boolean allowNonStoredUdfCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+                    boolean allowNonStoredUDFCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
                     throws CompilationException {
                 // Perform an initial error-checking pass to validate our body.
-                LOGGER.trace("Performing pre-rewrite check (user query validation).");
-                PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(langRewritingContext);
+                LOGGER.trace("Performing pre-Graphix-rewrite check (user query validation).");
+                GraphixRewritingContext graphixRewritingContext = (GraphixRewritingContext) langRewritingContext;
+                PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(graphixRewritingContext);
                 topStatement.getBody().accept(preRewriteCheckVisitor, null);
 
                 // Perform the Graphix rewrites.
-                rewriteGraphixASTNodes(langRewritingContext, topStatement);
+                rewriteGraphixASTNodes(graphixRewritingContext, topStatement, allowNonStoredUDFCalls);
 
                 // Sanity check: ensure that no graph AST nodes exist after this point.
-                LOGGER.trace("Performing post-rewrite check (making sure no graph AST nodes exist).");
+                LOGGER.trace("Performing post-Graphix-rewrite check (making sure no graph AST nodes exist).");
                 PostRewriteCheckVisitor postRewriteCheckVisitor = new PostRewriteCheckVisitor();
                 topStatement.getBody().accept(postRewriteCheckVisitor, null);
 
                 // Perform the remainder of the SQL++ rewrites.
                 LOGGER.debug("Ending Graphix AST rewrites. Now starting SQL++ AST rewrites.");
-                super.rewrite(langRewritingContext, topStatement, allowNonStoredUdfCalls, inlineUdfsAndViews,
+                rewriteSQLPPASTNodes(langRewritingContext, topStatement, allowNonStoredUDFCalls, inlineUdfsAndViews,
                         externalVars);
                 LOGGER.debug("Ending SQL++ AST rewrites.");
+
+                // Update the variable counter in our context.
+                topStatement.setVarCounter(langRewritingContext.getVarCounter().get());
             }
         };
     }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java
index 70e749b..7e8bda5 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/GraphixRewritingContext.java
@@ -16,45 +16,47 @@
  */
 package org.apache.asterix.graphix.lang.rewrites;
 
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
-import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.common.statement.ViewDecl;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 /**
- * Wrapper class for {@link org.apache.asterix.lang.common.rewrites.LangRewritingContext}. We are particularly
- * interested in creating our own system-generated variables (distinct from those generated in the SQL++ rewrites).
+ * Wrapper class for {@link LangRewritingContext} and for Graphix specific rewriting.
  */
-public class GraphixRewritingContext {
-    private final LangRewritingContext langRewritingContext;
+public class GraphixRewritingContext extends LangRewritingContext {
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs = new HashMap<>();
 
-    public GraphixRewritingContext(LangRewritingContext langRewritingContext) {
-        this.langRewritingContext = langRewritingContext;
+    public GraphixRewritingContext(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions,
+            List<ViewDecl> declaredViews, Set<DeclareGraphStatement> declareGraphStatements,
+            IWarningCollector warningCollector, int varCounter) {
+        super(metadataProvider, declaredFunctions, declaredViews, warningCollector, varCounter);
+        declareGraphStatements.forEach(d -> {
+            GraphIdentifier graphIdentifier = new GraphIdentifier(d.getDataverseName(), d.getGraphName());
+            this.declaredGraphs.put(graphIdentifier, d);
+        });
     }
 
-    public MetadataProvider getMetadataProvider() {
-        return langRewritingContext.getMetadataProvider();
+    public Map<GraphIdentifier, DeclareGraphStatement> getDeclaredGraphs() {
+        return declaredGraphs;
     }
 
-    public IWarningCollector getWarningCollector() {
-        return langRewritingContext.getWarningCollector();
-    }
-
-    public LangRewritingContext getLangRewritingContext() {
-        return langRewritingContext;
-    }
-
-    public Map<FunctionSignature, FunctionDecl> getDeclaredFunctions() {
-        return langRewritingContext.getDeclaredFunctions();
-    }
-
-    public VarIdentifier getNewVariable() {
-        VarIdentifier langRewriteGeneratedVar = langRewritingContext.newVariable();
+    public VarIdentifier getNewGraphixVariable() {
+        VarIdentifier langRewriteGeneratedVar = newVariable();
         String graphixGeneratedName = "#GG_" + langRewriteGeneratedVar.getValue().substring(1);
         return new VarIdentifier(graphixGeneratedName, langRewriteGeneratedVar.getId());
     }
+
+    public static boolean isGraphixVariable(VarIdentifier varIdentifier) {
+        return varIdentifier.getValue().startsWith("#GG_");
+    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/ExprAssembler.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/ExprAssembler.java
deleted file mode 100644
index e78d935..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/ExprAssembler.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
-
-import java.util.ArrayDeque;
-import java.util.Deque;
-import java.util.Iterator;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-
-/**
- * A generic assembler for building a list of T instances, where incoming instances take the last T instance as input.
- */
-public class ExprAssembler<T> implements Iterable<T> {
-    private final Deque<T> assemblyQueue = new ArrayDeque<>();
-
-    public void bind(IExprAssembly<T> assembly) throws CompilationException {
-        if (assemblyQueue.isEmpty()) {
-            assemblyQueue.addLast(assembly.apply(null));
-
-        } else {
-            T currentLastAssembly = assemblyQueue.getLast();
-            assemblyQueue.add(assembly.apply(currentLastAssembly));
-
-        }
-    }
-
-    public T getLast() {
-        return assemblyQueue.getLast();
-    }
-
-    @Override
-    public Iterator<T> iterator() {
-        return assemblyQueue.iterator();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java
new file mode 100644
index 0000000..89a1d98
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/DanglingVertexExpander.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.canonical;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+/**
+ * Expand a disconnected vertex into a set of canonical disconnected vertices. For dangling vertices, this just
+ * includes ensuring that each vertex has only one label.
+ */
+public class DanglingVertexExpander implements ICanonicalExpander<VertexPatternExpr> {
+    private final GraphixDeepCopyVisitor deepCopyVisitor = new GraphixDeepCopyVisitor();
+
+    @Override
+    public void apply(VertexPatternExpr vertexPatternExpr, List<GraphSelectBlock> inputSelectBlocks)
+            throws CompilationException {
+        if (vertexPatternExpr.getLabels().size() == 1) {
+            // No expansion is necessary. Our vertex is in canonical form.
+            return;
+        }
+        VariableExpr originalVariableExpr = vertexPatternExpr.getVariableExpr();
+
+        // We want to end up with |GSBs| * |labels| number of generated GSBs.
+        List<GraphSelectBlock> generatedGraphSelectBlocks = new ArrayList<>();
+        for (GraphSelectBlock oldGraphSelectBlock : inputSelectBlocks) {
+            for (ElementLabel vertexLabel : vertexPatternExpr.getLabels()) {
+                GraphSelectBlock clonedSelectBlock = deepCopyVisitor.visit(oldGraphSelectBlock, null);
+                for (MatchClause matchClause : clonedSelectBlock.getFromGraphClause().getMatchClauses()) {
+                    for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                        VariableExpr newVertexVariableExpr = deepCopyVisitor.visit(originalVariableExpr, null);
+                        VertexPatternExpr newVertexPatternExpr =
+                                new VertexPatternExpr(newVertexVariableExpr, Set.of(vertexLabel));
+                        replaceVertexExpression(pathExpression.getVertexExpressions(),
+                                pathExpression.getEdgeExpressions(), vertexPatternExpr, newVertexPatternExpr);
+                    }
+                }
+                generatedGraphSelectBlocks.add(clonedSelectBlock);
+            }
+        }
+        inputSelectBlocks.clear();
+        inputSelectBlocks.addAll(generatedGraphSelectBlocks);
+    }
+
+    private static void replaceVertexExpression(List<VertexPatternExpr> vertexExpressions,
+            List<EdgePatternExpr> edgeExpressions, VertexPatternExpr oldVertexExpression,
+            VertexPatternExpr newVertexExpression) {
+        ListIterator<VertexPatternExpr> vertexExpressionIterator = vertexExpressions.listIterator();
+        while (vertexExpressionIterator.hasNext()) {
+            VertexPatternExpr workingVertexExpression = vertexExpressionIterator.next();
+            if (workingVertexExpression.equals(oldVertexExpression)) {
+                vertexExpressionIterator.set(newVertexExpression);
+                break;
+            }
+        }
+        for (EdgePatternExpr workingEdgeExpression : edgeExpressions) {
+            if (workingEdgeExpression.getLeftVertex().equals(oldVertexExpression)) {
+                workingEdgeExpression.setLeftVertex(newVertexExpression);
+
+            } else if (workingEdgeExpression.getRightVertex().equals(oldVertexExpression)) {
+                workingEdgeExpression.setRightVertex(newVertexExpression);
+            }
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java
new file mode 100644
index 0000000..3210919
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/EdgeSubPathExpander.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.canonical;
+
+import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.EdgeDirection;
+import static org.apache.asterix.graphix.lang.struct.EdgeDescriptor.PatternType;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * Expand an edge and its connected vertices into an edge in canonical form.
+ * 1. An edge's left vertex must only have one label.
+ * 2. An edge's right vertex must only have one label.
+ * 3. An edge must only have one label.
+ * 4. An edge must be directed.
+ * 5. An edge must not be a sub-path.
+ */
+public class EdgeSubPathExpander implements ICanonicalExpander<EdgePatternExpr> {
+    private final Supplier<VariableExpr> newVariableSupplier;
+    private final GraphixDeepCopyVisitor deepCopyVisitor;
+
+    // To avoid "over-expanding", we need to keep knowledge about our FROM-GRAPH-CLAUSE.
+    private SchemaKnowledgeTable schemaKnowledgeTable;
+
+    public EdgeSubPathExpander(Supplier<VarIdentifier> getNewVariable) {
+        this.deepCopyVisitor = new GraphixDeepCopyVisitor();
+        this.newVariableSupplier = () -> new VariableExpr(getNewVariable.get());
+    }
+
+    public void resetSchema(SchemaKnowledgeTable schemaKnowledgeTable) {
+        this.schemaKnowledgeTable = schemaKnowledgeTable;
+    }
+
+    private static class CanonicalEdgeContext {
+        private final VariableExpr leftVertexVar;
+        private final VariableExpr rightVertexVar;
+        private final VariableExpr edgeVar;
+
+        private final ElementLabel leftVertexLabel;
+        private final ElementLabel rightVertexLabel;
+        private final ElementLabel edgeLabel;
+
+        private final EdgeDirection edgeDirection;
+
+        private CanonicalEdgeContext(VariableExpr leftVertexVar, VariableExpr rightVertexVar, VariableExpr edgeVar,
+                ElementLabel leftVertexLabel, ElementLabel rightVertexLabel, ElementLabel edgeLabel,
+                EdgeDirection edgeDirection) {
+            this.leftVertexVar = leftVertexVar;
+            this.rightVertexVar = rightVertexVar;
+            this.leftVertexLabel = leftVertexLabel;
+            this.rightVertexLabel = rightVertexLabel;
+            this.edgeDirection = edgeDirection;
+            this.edgeVar = edgeVar;
+            this.edgeLabel = edgeLabel;
+        }
+    }
+
+    @Override
+    public void apply(EdgePatternExpr edgePatternExpr, List<GraphSelectBlock> inputSelectBlocks)
+            throws CompilationException {
+        boolean doesLeftVertexRequireExpansion = edgePatternExpr.getLeftVertex().getLabels().size() > 1;
+        boolean doesRightVertexRequireExpansion = edgePatternExpr.getRightVertex().getLabels().size() > 1;
+        boolean doesEdgeRequireExpansion = edgePatternExpr.getEdgeDescriptor().getEdgeLabels().size() > 1
+                || edgePatternExpr.getEdgeDescriptor().getPatternType() != PatternType.EDGE
+                || edgePatternExpr.getEdgeDescriptor().getEdgeDirection() == EdgeDirection.UNDIRECTED;
+        if (!doesLeftVertexRequireExpansion && !doesRightVertexRequireExpansion && !doesEdgeRequireExpansion) {
+            // Our edge and both of our vertices are in canonical form.
+            return;
+        }
+
+        // Expand all possibilities that our edge pattern could represent.
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
+        VertexPatternExpr workingRightVertex = edgePatternExpr.getRightVertex();
+        List<List<CanonicalEdgeContext>> pathCandidates = new ArrayList<>();
+        if (edgeDescriptor.getPatternType() == PatternType.EDGE) {
+            expandEdgePattern(workingLeftVertex, workingRightVertex, edgeDescriptor, edgeDescriptor::getVariableExpr)
+                    .forEach(e -> pathCandidates.add(List.of(e)));
+
+        } else { // edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH
+            Deque<List<CanonicalEdgeContext>> pathCandidateStack = new ArrayDeque<>(List.of(List.of()));
+            for (int i = 0; i < edgeDescriptor.getMaximumHops(); i++) {
+                VertexPatternExpr nextInternalVertex = (i != edgeDescriptor.getMaximumHops() - 1)
+                        ? edgePatternExpr.getInternalVertices().get(i) : edgePatternExpr.getRightVertex();
+
+                // We will only yield a path if we have generated the minimum number of hops.
+                Deque<List<CanonicalEdgeContext>> generatedCandidateStack = new ArrayDeque<>();
+                boolean isYieldPath = i >= edgeDescriptor.getMinimumHops() - 1;
+                while (!pathCandidateStack.isEmpty()) {
+                    List<CanonicalEdgeContext> pathCandidate = pathCandidateStack.removeLast();
+                    if (isYieldPath) {
+                        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+                        expandEdgePattern(workingLeftVertex, rightVertex, edgeDescriptor, newVariableSupplier)
+                                .forEach(e -> {
+                                    List<CanonicalEdgeContext> pathCopy = new ArrayList<>(pathCandidate);
+                                    pathCopy.add(e);
+                                    pathCandidates.add(pathCopy);
+                                });
+                    }
+                    expandEdgePattern(workingLeftVertex, nextInternalVertex, edgeDescriptor, newVariableSupplier)
+                            .forEach(e -> {
+                                List<CanonicalEdgeContext> pathCopy = new ArrayList<>(pathCandidate);
+                                pathCopy.add(e);
+                                generatedCandidateStack.addLast(pathCopy);
+                            });
+                }
+                pathCandidateStack.addAll(generatedCandidateStack);
+
+                // Move our vertex cursor.
+                workingLeftVertex = nextInternalVertex;
+            }
+        }
+
+        // We must now prune all paths that contain an edge that does not adhere to our schema.
+        pathCandidates.removeIf(this::isPathCandidateInvalid);
+
+        // Perform the expansion into GRAPH-SELECT-BLOCKs.
+        List<GraphSelectBlock> selectBlockList = expandSelectBlocks(edgePatternExpr, inputSelectBlocks, pathCandidates);
+        inputSelectBlocks.clear();
+        inputSelectBlocks.addAll(selectBlockList);
+    }
+
+    private List<GraphSelectBlock> expandSelectBlocks(EdgePatternExpr edgePatternExpr,
+            List<GraphSelectBlock> inputSelectBlocks, List<List<CanonicalEdgeContext>> pathCandidates)
+            throws CompilationException {
+        List<GraphSelectBlock> generatedGraphSelectBlocks = new ArrayList<>();
+        for (GraphSelectBlock oldGraphSelectBlock : inputSelectBlocks) {
+            for (List<CanonicalEdgeContext> pathCandidate : pathCandidates) {
+                GraphSelectBlock clonedSelectBlock = deepCopyVisitor.visit(oldGraphSelectBlock, null);
+                for (MatchClause matchClause : clonedSelectBlock.getFromGraphClause().getMatchClauses()) {
+                    Pair<PathPatternExpr, PathPatternExpr> pathPatternReplacePair = null;
+
+                    // We are only interested in the path that contains our already expanded edge.
+                    for (PathPatternExpr pathPatternExpr : matchClause.getPathExpressions()) {
+                        List<EdgePatternExpr> edgeExpressions = pathPatternExpr.getEdgeExpressions();
+                        int edgeExprIndex = edgeExpressions.indexOf(edgePatternExpr);
+                        if (edgeExprIndex < 0) {
+                            continue;
+                        }
+
+                        // Note: in this case, we do not need to worry about dangling vertices.
+                        List<LetClause> reboundSubPathList = new ArrayList<>(pathPatternExpr.getReboundSubPathList());
+                        List<EdgePatternExpr> newEdgePatternExprList = new ArrayList<>();
+                        List<VertexPatternExpr> newVertexPatternExprList = new ArrayList<>();
+                        for (int i = 0; i < edgeExpressions.size(); i++) {
+                            EdgePatternExpr existingEdgeExpression = edgeExpressions.get(i);
+                            if (i != edgeExprIndex) {
+                                newEdgePatternExprList.add(existingEdgeExpression);
+                                newVertexPatternExprList.add(existingEdgeExpression.getLeftVertex());
+                                newVertexPatternExprList.add(existingEdgeExpression.getRightVertex());
+                                continue;
+                            }
+
+                            // Note: this will produce duplicate vertex expressions.
+                            List<VertexPatternExpr> subPathVertexList = new ArrayList<>();
+                            List<EdgePatternExpr> subPathEdgeList = new ArrayList<>();
+                            for (CanonicalEdgeContext edgeContext : pathCandidate) {
+                                VariableExpr newEdgeVar = deepCopyVisitor.visit(edgeContext.edgeVar, null);
+                                VariableExpr newLeftVar = deepCopyVisitor.visit(edgeContext.leftVertexVar, null);
+                                VariableExpr newRightVar = deepCopyVisitor.visit(edgeContext.rightVertexVar, null);
+                                EdgeDescriptor newDescriptor = new EdgeDescriptor(edgeContext.edgeDirection,
+                                        PatternType.EDGE, Set.of(edgeContext.edgeLabel), newEdgeVar, 1, 1);
+                                Set<ElementLabel> leftLabelSingleton = Set.of(edgeContext.leftVertexLabel);
+                                Set<ElementLabel> rightLabelSingleton = Set.of(edgeContext.rightVertexLabel);
+                                VertexPatternExpr leftVertex = new VertexPatternExpr(newLeftVar, leftLabelSingleton);
+                                VertexPatternExpr rightVertex = new VertexPatternExpr(newRightVar, rightLabelSingleton);
+                                EdgePatternExpr newEdge = new EdgePatternExpr(leftVertex, rightVertex, newDescriptor);
+
+                                // Update our path and our sub-path.
+                                newEdgePatternExprList.add(newEdge);
+                                newVertexPatternExprList.add(leftVertex);
+                                newVertexPatternExprList.add(rightVertex);
+                                subPathEdgeList.add(newEdge);
+                                subPathVertexList.add(leftVertex);
+                                subPathVertexList.add(rightVertex);
+                            }
+
+                            // Bind our sub-path to a path record.
+                            if (existingEdgeExpression.getEdgeDescriptor().getPatternType() == PatternType.PATH) {
+                                RecordConstructor pathRecord = new RecordConstructor();
+                                EdgeDescriptor edgeDescriptor = existingEdgeExpression.getEdgeDescriptor();
+                                PathPatternAction.buildPathRecord(subPathVertexList, subPathEdgeList, pathRecord);
+                                reboundSubPathList.add(new LetClause(edgeDescriptor.getVariableExpr(), pathRecord));
+                            }
+                        }
+
+                        // Build a new path pattern to replace our old path pattern.
+                        VariableExpr newPathVariable = null;
+                        if (pathPatternExpr.getVariableExpr() != null) {
+                            newPathVariable = deepCopyVisitor.visit(pathPatternExpr.getVariableExpr(), null);
+                        }
+                        PathPatternExpr newPathPatternExpr =
+                                new PathPatternExpr(newVertexPatternExprList, newEdgePatternExprList, newPathVariable);
+                        newPathPatternExpr.getReboundSubPathList().addAll(reboundSubPathList);
+                        pathPatternReplacePair = new Pair<>(pathPatternExpr, newPathPatternExpr);
+                    }
+
+                    // Finalize our expansion by replacing our path in our MATCH-CLAUSE.
+                    if (pathPatternReplacePair == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                                "Edge was expanded, but edge was not found in given SELECT-BLOCK!");
+                    }
+                    PathPatternExpr oldPathPattern = pathPatternReplacePair.first;
+                    PathPatternExpr newPathPattern = pathPatternReplacePair.second;
+                    matchClause.getPathExpressions().replaceAll(p -> (p == oldPathPattern) ? newPathPattern : p);
+                }
+                generatedGraphSelectBlocks.add(clonedSelectBlock);
+            }
+        }
+        return generatedGraphSelectBlocks;
+    }
+
+    private List<CanonicalEdgeContext> expandEdgePattern(VertexPatternExpr leftVertex, VertexPatternExpr rightVertex,
+            EdgeDescriptor edgeDescriptor, Supplier<VariableExpr> edgeVarSupplier) throws CompilationException {
+        List<CanonicalEdgeContext> edgeCandidates = new ArrayList<>();
+        for (ElementLabel leftVertexLabel : leftVertex.getLabels()) {
+            for (ElementLabel rightVertexLabel : rightVertex.getLabels()) {
+                for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDirection.LEFT_TO_RIGHT) {
+                        VariableExpr edgeVar = deepCopyVisitor.visit(edgeVarSupplier.get(), null);
+                        VariableExpr leftVertexVar = deepCopyVisitor.visit(leftVertex.getVariableExpr(), null);
+                        VariableExpr rightVertexVar = deepCopyVisitor.visit(rightVertex.getVariableExpr(), null);
+                        CanonicalEdgeContext rightToLeftContext =
+                                new CanonicalEdgeContext(leftVertexVar, rightVertexVar, edgeVar, leftVertexLabel,
+                                        rightVertexLabel, edgeLabel, EdgeDirection.RIGHT_TO_LEFT);
+                        edgeCandidates.add(rightToLeftContext);
+                    }
+                    if (edgeDescriptor.getEdgeDirection() != EdgeDirection.RIGHT_TO_LEFT) {
+                        VariableExpr edgeVar = deepCopyVisitor.visit(edgeVarSupplier.get(), null);
+                        VariableExpr leftVertexVar = deepCopyVisitor.visit(leftVertex.getVariableExpr(), null);
+                        VariableExpr rightVertexVar = deepCopyVisitor.visit(rightVertex.getVariableExpr(), null);
+                        CanonicalEdgeContext leftToRightContext =
+                                new CanonicalEdgeContext(leftVertexVar, rightVertexVar, edgeVar, leftVertexLabel,
+                                        rightVertexLabel, edgeLabel, EdgeDirection.LEFT_TO_RIGHT);
+                        edgeCandidates.add(leftToRightContext);
+                    }
+                }
+            }
+        }
+        return edgeCandidates;
+    }
+
+    private boolean isPathCandidateInvalid(List<CanonicalEdgeContext> pathCandidate) {
+        for (CanonicalEdgeContext edgeCandidate : pathCandidate) {
+            for (SchemaKnowledgeTable.KnowledgeRecord knowledgeRecord : schemaKnowledgeTable) {
+                if (edgeCandidate.edgeDirection == EdgeDirection.LEFT_TO_RIGHT) {
+                    if (edgeCandidate.leftVertexLabel.equals(knowledgeRecord.getSourceVertexLabel())
+                            && edgeCandidate.edgeLabel.equals(knowledgeRecord.getEdgeLabel())
+                            && edgeCandidate.rightVertexLabel.equals(knowledgeRecord.getDestVertexLabel())) {
+                        return false;
+                    }
+
+                } else { // edgeCandidate.edgeDirection == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
+                    if (edgeCandidate.leftVertexLabel.equals(knowledgeRecord.getDestVertexLabel())
+                            && edgeCandidate.edgeLabel.equals(knowledgeRecord.getEdgeLabel())
+                            && edgeCandidate.rightVertexLabel.equals(knowledgeRecord.getSourceVertexLabel())) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+        return true;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
similarity index 75%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
index 25efe98..65f42e5 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/canonical/ICanonicalExpander.java
@@ -16,14 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+package org.apache.asterix.graphix.lang.rewrites.canonical;
+
+import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
 
-/**
- * @see ExprAssembler
- */
 @FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public interface ICanonicalExpander<T> {
+    void apply(T patternExpr, List<GraphSelectBlock> inputSelectBlocks) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
index 847c0d9..25ec13b 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
@@ -19,10 +19,12 @@
 package org.apache.asterix.graphix.lang.rewrites.common;
 
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -35,8 +37,6 @@
  * graphs (represented as an adjacency list). Ideally, we want to return the smallest set of Hamilton
  * {@link EdgePatternExpr} paths, but this is an NP-hard problem. Instead, we find the **first** set of paths that
  * visit each {@link EdgePatternExpr} once. This inner-order is dependent on the order of the adjacency map iterators.
- *
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.ElementAnalysisVisitor
  */
 public class EdgeDependencyGraph implements Iterable<Iterable<EdgePatternExpr>> {
     private final List<Map<Identifier, List<Identifier>>> adjacencyMaps;
@@ -48,54 +48,84 @@
         this.edgePatternMap = edgePatternMap;
     }
 
-    private Iterator<EdgePatternExpr> buildEdgeOrdering(Map<Identifier, List<Identifier>> adjacencyMap) {
-        Set<Identifier> visitedSet = new HashSet<>();
-        Deque<Identifier> edgeStack = new ArrayDeque<>();
-
-        if (!adjacencyMap.entrySet().isEmpty()) {
-            // We start with the first inserted edge into our graph.
-            Iterator<Map.Entry<Identifier, List<Identifier>>> adjacencyMapIterator = adjacencyMap.entrySet().iterator();
-            Map.Entry<Identifier, List<Identifier>> seedEdge = adjacencyMapIterator.next();
-            edgeStack.addFirst(seedEdge.getKey());
-            edgeStack.addAll(seedEdge.getValue());
-
-            // Continue until all entries in our adjacency map have been inserted.
-            while (adjacencyMapIterator.hasNext()) {
-                Map.Entry<Identifier, List<Identifier>> followingEdge = adjacencyMapIterator.next();
-                for (Identifier followingEdgeDependent : followingEdge.getValue()) {
-                    if (!edgeStack.contains(followingEdgeDependent)) {
-                        edgeStack.addLast(followingEdgeDependent);
-                    }
-                }
-            }
-
-            return new Iterator<>() {
-                @Override
-                public boolean hasNext() {
-                    // We are done once we have visited every node.
-                    return visitedSet.size() != adjacencyMap.size();
-                }
-
-                @Override
-                public EdgePatternExpr next() {
-                    Identifier edgeIdentifier = edgeStack.removeFirst();
-                    for (Identifier dependency : adjacencyMap.get(edgeIdentifier)) {
-                        if (!visitedSet.contains(dependency)) {
-                            edgeStack.push(dependency);
-                        }
-                    }
-                    visitedSet.add(edgeIdentifier);
-                    return edgePatternMap.get(edgeIdentifier);
-                }
-            };
-
-        } else {
-            return Collections.emptyIterator();
-        }
-    }
-
     @Override
     public Iterator<Iterable<EdgePatternExpr>> iterator() {
-        return adjacencyMaps.stream().map(m -> (Iterable<EdgePatternExpr>) () -> buildEdgeOrdering(m)).iterator();
+        List<Iterable<EdgePatternExpr>> edgePatternIterables = new ArrayList<>();
+        Set<Identifier> globalVisitedSet = new HashSet<>();
+
+        for (Map<Identifier, List<Identifier>> adjacencyMap : adjacencyMaps) {
+            Deque<Identifier> edgeStack = new ArrayDeque<>();
+            Set<Identifier> localVisitedSet = new HashSet<>();
+
+            if (!adjacencyMap.entrySet().isEmpty()) {
+                if (globalVisitedSet.isEmpty()) {
+                    // We start with the first inserted edge inserted into our graph, and continue from there.
+                    Iterator<Map.Entry<Identifier, List<Identifier>>> mapIterator = adjacencyMap.entrySet().iterator();
+                    Map.Entry<Identifier, List<Identifier>> seedEdge = mapIterator.next();
+                    edgeStack.addFirst(seedEdge.getKey());
+                    edgeStack.addAll(seedEdge.getValue());
+                    while (mapIterator.hasNext()) {
+                        Map.Entry<Identifier, List<Identifier>> followingEdge = mapIterator.next();
+                        for (Identifier followingEdgeDependent : followingEdge.getValue()) {
+                            if (!edgeStack.contains(followingEdgeDependent)) {
+                                edgeStack.addLast(followingEdgeDependent);
+                            }
+                        }
+                    }
+
+                } else {
+                    // This is not our first pass. Find a connecting edge to seed our stack.
+                    Set<Identifier> disconnectedExploredEdgeSet = new LinkedHashSet<>();
+                    for (Map.Entry<Identifier, List<Identifier>> workingEdge : adjacencyMap.entrySet()) {
+                        for (Identifier workingEdgeDependent : workingEdge.getValue()) {
+                            if (globalVisitedSet.contains(workingEdgeDependent) && edgeStack.isEmpty()) {
+                                edgeStack.addFirst(workingEdgeDependent);
+                                localVisitedSet.add(workingEdgeDependent);
+
+                            } else if (!globalVisitedSet.contains(workingEdgeDependent)) {
+                                disconnectedExploredEdgeSet.add(workingEdgeDependent);
+
+                            } else {
+                                localVisitedSet.add(workingEdgeDependent);
+                            }
+                        }
+                        if (workingEdge.getValue().isEmpty() && globalVisitedSet.contains(workingEdge.getKey())) {
+                            localVisitedSet.add(workingEdge.getKey());
+
+                        } else if (workingEdge.getValue().isEmpty()) {
+                            disconnectedExploredEdgeSet.add(workingEdge.getKey());
+                        }
+                    }
+                    disconnectedExploredEdgeSet.forEach(edgeStack::addLast);
+                }
+
+                // Build our iterable.
+                globalVisitedSet.addAll(edgeStack);
+                edgePatternIterables.add(() -> new Iterator<>() {
+                    @Override
+                    public boolean hasNext() {
+                        // We are done once we have visited every node.
+                        return localVisitedSet.size() != adjacencyMap.size();
+                    }
+
+                    @Override
+                    public EdgePatternExpr next() {
+                        Identifier edgeIdentifier = edgeStack.removeFirst();
+                        for (Identifier dependency : adjacencyMap.get(edgeIdentifier)) {
+                            if (!localVisitedSet.contains(dependency)) {
+                                edgeStack.addFirst(dependency);
+                            }
+                        }
+                        localVisitedSet.add(edgeIdentifier);
+                        return edgePatternMap.get(edgeIdentifier);
+                    }
+                });
+
+            } else {
+                edgePatternIterables.add(Collections.emptyList());
+            }
+        }
+
+        return edgePatternIterables.iterator();
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java
index 747cdae..cfa4aae 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/rewrites/common/ElementLookupTable.java
@@ -23,74 +23,51 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 
 /**
- * Lookup table for {@link GraphElementDecl} instances, vertex keys, edge destination keys / labels, and edge source
- * keys / labels-- indexed by {@link T} instances.
+ * Lookup table for {@link GraphElementDeclaration} instances, vertex keys, edge destination keys, and edge source
+ * keys-- indexed by {@link GraphElementIdentifier} instances.
  */
-public class ElementLookupTable<T> implements Iterable<GraphElementDecl> {
-    private final Map<T, GraphElementDecl> graphElementDeclMap = new HashMap<>();
-    private final Map<T, List<List<String>>> vertexKeyMap = new HashMap<>();
-    private final Map<T, List<List<String>>> edgeDestKeysMap = new HashMap<>();
-    private final Map<T, List<List<String>>> edgeSourceKeysMap = new HashMap<>();
+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<>();
 
-    // In addition to keys, maintain the source and destination labels associated with each edge.
-    private final Map<T, ElementLabel> edgeDestLabelMap = new HashMap<>();
-    private final Map<T, ElementLabel> edgeSourceLabelMap = new HashMap<>();
-
-    public void put(T identifier, GraphElementDecl graphElementDecl) {
-        if (graphElementDeclMap.containsKey(identifier)) {
-            graphElementDeclMap.get(identifier).getBodies().addAll(graphElementDecl.getBodies());
-
-        } else {
-            graphElementDeclMap.put(identifier, graphElementDecl);
-        }
+    public void put(GraphElementIdentifier identifier, GraphElementDeclaration graphElementDeclaration) {
+        graphElementDeclMap.put(identifier, graphElementDeclaration);
     }
 
-    public void putVertexKey(T identifier, List<List<String>> primaryKey) {
+    public void putVertexKey(GraphElementIdentifier identifier, List<List<String>> primaryKey) {
         vertexKeyMap.put(identifier, primaryKey);
     }
 
-    public void putEdgeKeys(T identifier, List<List<String>> sourceKey, List<List<String>> destinationKey) {
-        // We know that a source key and destination key are the same for all edge definitions of the same label.
+    public void putEdgeKeys(GraphElementIdentifier identifier, List<List<String>> sourceKey,
+            List<List<String>> destinationKey) {
         edgeSourceKeysMap.put(identifier, sourceKey);
         edgeDestKeysMap.put(identifier, destinationKey);
     }
 
-    public void putEdgeLabels(T identifier, ElementLabel sourceLabel, ElementLabel destinationLabel) {
-        // Multiple edge definitions for the same label will always have the same source and destination labels.
-        edgeSourceLabelMap.put(identifier, sourceLabel);
-        edgeDestLabelMap.put(identifier, destinationLabel);
-    }
-
-    public GraphElementDecl getElementDecl(T identifier) {
+    public GraphElementDeclaration getElementDecl(GraphElementIdentifier identifier) {
         return graphElementDeclMap.get(identifier);
     }
 
-    public List<List<String>> getVertexKey(T identifier) {
+    public List<List<String>> getVertexKey(GraphElementIdentifier identifier) {
         return vertexKeyMap.get(identifier);
     }
 
-    public List<List<String>> getEdgeDestKeys(T identifier) {
+    public List<List<String>> getEdgeDestKey(GraphElementIdentifier identifier) {
         return edgeDestKeysMap.get(identifier);
     }
 
-    public List<List<String>> getEdgeSourceKeys(T identifier) {
+    public List<List<String>> getEdgeSourceKey(GraphElementIdentifier identifier) {
         return edgeSourceKeysMap.get(identifier);
     }
 
-    public ElementLabel getEdgeDestLabel(T identifier) {
-        return edgeDestLabelMap.get(identifier);
-    }
-
-    public ElementLabel getEdgeSourceLabel(T identifier) {
-        return edgeSourceLabelMap.get(identifier);
-    }
-
     @Override
-    public Iterator<GraphElementDecl> iterator() {
+    public Iterator<GraphElementDeclaration> iterator() {
         return graphElementDeclMap.values().iterator();
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedFixedPathExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedFixedPathExpansion.java
deleted file mode 100644
index 744f3b5..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedFixedPathExpansion.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-
-/**
- * Given a sub-path that is **not** {@link EdgeDescriptor.EdgeDirection#UNDIRECTED} but is fixed in the number of hops
- * (denoted N), we build a single set of N directed simple edges.
- */
-public class DirectedFixedPathExpansion implements IEdgePatternExpansion {
-    private final PathEnumerationEnvironment pathEnumerationEnvironment;
-
-    public DirectedFixedPathExpansion(PathEnumerationEnvironment pathEnumerationEnvironment) {
-        this.pathEnumerationEnvironment = pathEnumerationEnvironment;
-    }
-
-    @Override
-    public Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr edgePatternExpr) {
-        // Ensure we have been given the correct type of sub-path.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() != EdgeDescriptor.PatternType.PATH
-                || edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED
-                || !Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-            throw new IllegalArgumentException("Expected a directed fixed sub-path, but given a " + edgeDescriptor);
-        }
-
-        pathEnumerationEnvironment.reset();
-        List<EdgePatternExpr> decomposedEdgeList = new ArrayList<>();
-        VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
-        for (int i = 0; i < edgePatternExpr.getEdgeDescriptor().getMinimumHops(); i++) {
-            VertexPatternExpr rightVertex;
-            if (i == edgePatternExpr.getEdgeDescriptor().getMinimumHops() - 1) {
-                // This is the final vertex in our path.
-                rightVertex = edgePatternExpr.getRightVertex();
-
-            } else {
-                // We need to generate an intermediate vertex.
-                rightVertex = new VertexPatternExpr(pathEnumerationEnvironment.getNewVertexVar(),
-                        edgePatternExpr.getInternalVertices().get(i).getLabels());
-            }
-
-            // Build our EDGE-PATTERN-EXPR.
-            EdgeDescriptor newEdgeDescriptor =
-                    new EdgeDescriptor(edgePatternExpr.getEdgeDescriptor().getEdgeDirection(),
-                            EdgeDescriptor.PatternType.EDGE, edgePatternExpr.getEdgeDescriptor().getEdgeLabels(),
-                            pathEnumerationEnvironment.getNewEdgeVar(), null, null);
-            EdgePatternExpr newEdgePattern = new EdgePatternExpr(workingLeftVertex, rightVertex, newEdgeDescriptor);
-            decomposedEdgeList.add(newEdgePattern);
-
-            // Build the associated path record (for our environment to hold).
-            pathEnumerationEnvironment.buildPathRecord(workingLeftVertex.getVariableExpr(),
-                    newEdgeDescriptor.getVariableExpr(), rightVertex.getVariableExpr());
-
-            // Move our "vertex cursor".
-            workingLeftVertex = rightVertex;
-        }
-
-        // We do not have any undirected edges. Return a singleton list with the EDGE-PATTERN-EXPRs above.
-        pathEnumerationEnvironment.publishIsomorphism();
-        pathEnumerationEnvironment.publishRebindings();
-        return List.of(decomposedEdgeList);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedVarPathExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedVarPathExpansion.java
deleted file mode 100644
index 0ac81f7..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedVarPathExpansion.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-
-/**
- * Given a path of variable length, there are {@link EdgeDescriptor#getMaximumHops()} -
- * {@link EdgeDescriptor#getMinimumHops()} valid paths that could appear in our result. Generate a fixed sub-path for
- * each of the aforementioned paths and decompose according to {@link DirectedFixedPathExpansion}.
- *
- * @see DirectedFixedPathExpansion
- */
-public class DirectedVarPathExpansion implements IEdgePatternExpansion {
-    private final PathEnumerationEnvironment pathEnumerationEnvironment;
-
-    public DirectedVarPathExpansion(PathEnumerationEnvironment pathEnumerationEnvironment) {
-        this.pathEnumerationEnvironment = pathEnumerationEnvironment;
-    }
-
-    @Override
-    public Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr edgePatternExpr) {
-        // Ensure we have been given the correct type of sub-path.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() != EdgeDescriptor.PatternType.PATH
-                || edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED
-                || Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-            throw new IllegalArgumentException("Expected a directed var sub-path, but given a " + edgeDescriptor);
-        }
-
-        List<List<EdgePatternExpr>> decomposedEdgeList = new ArrayList<>();
-        for (int i = edgeDescriptor.getMinimumHops(); i <= edgeDescriptor.getMaximumHops(); i++) {
-            EdgeDescriptor fixedEdgeDescriptor =
-                    new EdgeDescriptor(edgeDescriptor.getEdgeDirection(), EdgeDescriptor.PatternType.PATH,
-                            edgeDescriptor.getEdgeLabels(), edgeDescriptor.getVariableExpr(), i, i);
-            EdgePatternExpr fixedEdgePattern = new EdgePatternExpr(edgePatternExpr.getLeftVertex(),
-                    edgePatternExpr.getRightVertex(), fixedEdgeDescriptor);
-            fixedEdgePattern.replaceInternalVertices(edgePatternExpr.getInternalVertices());
-
-            // We defer the decomposition of each individual path to DirectedFixedPathExpansion.
-            new DirectedFixedPathExpansion(pathEnumerationEnvironment).expand(fixedEdgePattern).forEach(e -> {
-                List<EdgePatternExpr> decompositionIntermediate = new ArrayList<>();
-                e.forEach(decompositionIntermediate::add);
-                decomposedEdgeList.add(decompositionIntermediate);
-            });
-        }
-
-        return new ArrayList<>(decomposedEdgeList);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/IEdgePatternExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/IEdgePatternExpansion.java
deleted file mode 100644
index cd62074..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/IEdgePatternExpansion.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-
-/**
- * Interface for decomposing an edge / sub-path into a two-level collection of simple directed edges. An inner list of
- * {@link EdgePatternExpr} instances will be lowered into a single {@link org.apache.asterix.lang.sqlpp.clause.FromTerm}
- * with correlated clauses. Multiple inner-lists will generate several FROM-TERM nodes, which will be UNION-ALLed into
- * a single FROM-TERM.
- */
-@FunctionalInterface
-public interface IEdgePatternExpansion {
-    Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr inputEdgePatternExpr);
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/PathEnumerationEnvironment.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/PathEnumerationEnvironment.java
deleted file mode 100644
index 7cc63dd..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/PathEnumerationEnvironment.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import static org.apache.asterix.graphix.lang.rewrites.lower.assembly.IsomorphismLowerAssembly.populateVariableMap;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.IsomorphismLowerAssembly;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.ListConstructor;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-
-public class PathEnumerationEnvironment {
-    private final LowerSupplierContext lowerSupplierContext;
-    private final EdgePatternExpr edgePatternExpr;
-
-    // We build the following as we decompose a path instance.
-    private final Map<ElementLabel, List<VariableExpr>> collectedVertexVariables = new HashMap<>();
-    private final Map<ElementLabel, List<VariableExpr>> collectedEdgeVariables = new HashMap<>();
-    private final Set<VariableExpr> nonTerminalVertexVariables = new HashSet<>();
-    private final List<PathRecord> generatedPathRecords = new ArrayList<>();
-
-    // The following callbacks are invoked on publish.
-    private final Consumer<List<Expression>> vertexConjunctsCallback;
-    private final Consumer<List<Expression>> edgeConjunctsCallback;
-    private final Consumer<List<LetClause>> reboundExpressionsCallback;
-    private int internalVertexCursor;
-
-    public PathEnumerationEnvironment(EdgePatternExpr edgePatternExpr, LowerSupplierContext lowerSupplierContext,
-            Consumer<List<Expression>> vertexConjunctsCallback, Consumer<List<Expression>> edgeConjunctsCallback,
-            Consumer<List<LetClause>> reboundExpressionsCallback) {
-        this.lowerSupplierContext = lowerSupplierContext;
-        this.edgePatternExpr = edgePatternExpr;
-        this.vertexConjunctsCallback = vertexConjunctsCallback;
-        this.edgeConjunctsCallback = edgeConjunctsCallback;
-        this.reboundExpressionsCallback = reboundExpressionsCallback;
-        this.internalVertexCursor = 0;
-    }
-
-    public void reset() {
-        nonTerminalVertexVariables.clear();
-        collectedVertexVariables.clear();
-        collectedEdgeVariables.clear();
-        generatedPathRecords.clear();
-        this.internalVertexCursor = 0;
-    }
-
-    public void publishIsomorphism() {
-        // Ensure that isomorphism between our internal vertices and our terminal vertices is applied.
-        VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
-        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
-        VarIdentifier leftVertexVar = leftVertex.getVariableExpr().getVar();
-        VarIdentifier rightVertexVar = rightVertex.getVariableExpr().getVar();
-        populateVariableMap(leftVertex.getLabels(), new VariableExpr(leftVertexVar), collectedVertexVariables);
-        populateVariableMap(rightVertex.getLabels(), new VariableExpr(rightVertexVar), collectedVertexVariables);
-
-        // Add isomorphism constraints between all vertices and all edges.
-        List<Expression> vertexConjuncts = collectedVertexVariables.values().stream()
-                .map(v -> v.stream().map(w -> (Expression) w).collect(Collectors.toList()))
-                .map(IsomorphismLowerAssembly::generateIsomorphismConjuncts).flatMap(Collection::stream).filter(c -> {
-                    // Do not include the conjunct to compare the terminal vertices. These will be handled later.
-                    OperatorExpr operatorExpr = (OperatorExpr) c;
-                    VariableExpr leftSourceVar = (VariableExpr) operatorExpr.getExprList().get(0);
-                    VariableExpr rightSourceVar = (VariableExpr) operatorExpr.getExprList().get(1);
-                    return nonTerminalVertexVariables.contains(leftSourceVar)
-                            || nonTerminalVertexVariables.contains(rightSourceVar);
-                }).collect(Collectors.toList());
-        List<Expression> edgeConjuncts = collectedEdgeVariables.values().stream()
-                .map(e -> e.stream().map(w -> (Expression) w).collect(Collectors.toList()))
-                .map(IsomorphismLowerAssembly::generateIsomorphismConjuncts).flatMap(Collection::stream)
-                .collect(Collectors.toList());
-        vertexConjunctsCallback.accept(vertexConjuncts);
-        edgeConjunctsCallback.accept(edgeConjuncts);
-    }
-
-    public void publishRebindings() {
-        // Bind our previous edge variable to our list of path records.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        VariableExpr subPathVariable = new VariableExpr(edgeDescriptor.getVariableExpr().getVar());
-        Expression subPathExpr = new ListConstructor(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR,
-                generatedPathRecords.stream().map(PathRecord::getExpression).collect(Collectors.toList()));
-        reboundExpressionsCallback.accept(List.of(new LetClause(subPathVariable, subPathExpr)));
-    }
-
-    public VariableExpr getNewVertexVar() {
-        VariableExpr vertexVariable = new VariableExpr(lowerSupplierContext.getNewVariable());
-        VertexPatternExpr workingInternalVertex = edgePatternExpr.getInternalVertices().get(internalVertexCursor);
-        populateVariableMap(workingInternalVertex.getLabels(), vertexVariable, collectedVertexVariables);
-        nonTerminalVertexVariables.add(vertexVariable);
-        return vertexVariable;
-    }
-
-    public VariableExpr getNewEdgeVar() {
-        VariableExpr edgeVariable = new VariableExpr(lowerSupplierContext.getNewVariable());
-        populateVariableMap(edgePatternExpr.getEdgeDescriptor().getEdgeLabels(), edgeVariable, collectedEdgeVariables);
-        return edgeVariable;
-    }
-
-    public void buildPathRecord(VariableExpr leftVertexVar, VariableExpr edgeVar, VariableExpr rightVertexVar) {
-        generatedPathRecords.add(new PathRecord(leftVertexVar, edgeVar, rightVertexVar));
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedEdgeExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedEdgeExpansion.java
deleted file mode 100644
index fa03a40..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedEdgeExpansion.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import java.util.List;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-
-/**
- * Given an undirected edge, generate two edges (to feed to the caller's UNION): one edge directed from
- * {@link EdgeDescriptor.EdgeDirection#LEFT_TO_RIGHT},and another edge directed from
- * {@link EdgeDescriptor.EdgeDirection#RIGHT_TO_LEFT}.
- */
-public class UndirectedEdgeExpansion implements IEdgePatternExpansion {
-    @Override
-    public Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr edgePatternExpr) {
-        // Ensure we have been given the correct type of edge.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() != EdgeDescriptor.PatternType.EDGE
-                || edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED) {
-            throw new IllegalArgumentException("Expected an undirected edge, but given a " + edgeDescriptor);
-        }
-        VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
-        VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
-
-        // Build our LEFT_TO_RIGHT edge...
-        EdgeDescriptor leftToRightEdgeDescriptor =
-                new EdgeDescriptor(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT, EdgeDescriptor.PatternType.EDGE,
-                        edgeDescriptor.getEdgeLabels(), edgeDescriptor.getVariableExpr(), null, null);
-        EdgePatternExpr leftToRightEdge = new EdgePatternExpr(leftVertex, rightVertex, leftToRightEdgeDescriptor);
-
-        // ...and our RIGHT_TO_LEFT edge...
-        EdgeDescriptor rightToLeftEdgeDescriptor =
-                new EdgeDescriptor(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT, EdgeDescriptor.PatternType.EDGE,
-                        edgeDescriptor.getEdgeLabels(), edgeDescriptor.getVariableExpr(), null, null);
-        EdgePatternExpr rightToLeftEdge = new EdgePatternExpr(leftVertex, rightVertex, rightToLeftEdgeDescriptor);
-
-        // Return two singleton lists (these should be UNION-ALLed).
-        return List.of(List.of(leftToRightEdge), List.of(rightToLeftEdge));
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedFixedPathExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedFixedPathExpansion.java
deleted file mode 100644
index 0b0fdeb..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedFixedPathExpansion.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-
-/**
- * Given a sub-path that is {@link EdgeDescriptor.EdgeDirection#UNDIRECTED} but is fixed in the number of hops (denoted N),
- * we generate N sets of directed simple edges (the total of which is equal to 2^N).
- */
-public class UndirectedFixedPathExpansion implements IEdgePatternExpansion {
-    private final PathEnumerationEnvironment pathEnumerationEnvironment;
-
-    public UndirectedFixedPathExpansion(PathEnumerationEnvironment pathEnumerationEnvironment) {
-        this.pathEnumerationEnvironment = pathEnumerationEnvironment;
-    }
-
-    @Override
-    public Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr edgePatternExpr) {
-        // Ensure we have been given the correct type of sub-path.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() != EdgeDescriptor.PatternType.PATH
-                || edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED
-                || !Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-            throw new IllegalArgumentException("Expected an undirected fixed sub-path, but given a " + edgeDescriptor);
-        }
-        pathEnumerationEnvironment.reset();
-
-        // The number of items in the final state of our queue will be 2^{number of hops}.
-        Deque<List<EdgePatternExpr>> decompositionQueue = new ArrayDeque<>();
-        decompositionQueue.addLast(new ArrayList<>());
-        VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
-        for (int i = 0; i < edgePatternExpr.getEdgeDescriptor().getMinimumHops(); i++) {
-            VertexPatternExpr rightVertex;
-            if (i == edgePatternExpr.getEdgeDescriptor().getMinimumHops() - 1) {
-                // This is the final vertex in our path.
-                rightVertex = edgePatternExpr.getRightVertex();
-
-            } else {
-                // We need to generate an intermediate vertex.
-                rightVertex = new VertexPatternExpr(pathEnumerationEnvironment.getNewVertexVar(),
-                        edgePatternExpr.getInternalVertices().get(i).getLabels());
-            }
-
-            VariableExpr graphEdgeVar = pathEnumerationEnvironment.getNewEdgeVar();
-            List<List<EdgePatternExpr>> visitedEdgeLists = new ArrayList<>();
-            while (!decompositionQueue.isEmpty()) {
-                // Pull from our queue...
-                List<EdgePatternExpr> workingEdgePatternList = decompositionQueue.removeLast();
-
-                // ...and generate two new variants that have an additional LEFT_TO_RIGHT edge...
-                EdgeDescriptor leftToRightEdgeDescriptor =
-                        new EdgeDescriptor(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT, EdgeDescriptor.PatternType.EDGE,
-                                edgePatternExpr.getEdgeDescriptor().getEdgeLabels(), graphEdgeVar, null, null);
-                List<EdgePatternExpr> leftToRightList = new ArrayList<>(workingEdgePatternList);
-                leftToRightList.add(new EdgePatternExpr(workingLeftVertex, rightVertex, leftToRightEdgeDescriptor));
-                visitedEdgeLists.add(leftToRightList);
-
-                // ...and a RIGHT_TO_LEFT edge.
-                EdgeDescriptor rightToLeftEdgeDescriptor =
-                        new EdgeDescriptor(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT, EdgeDescriptor.PatternType.EDGE,
-                                edgePatternExpr.getEdgeDescriptor().getEdgeLabels(), graphEdgeVar, null, null);
-                List<EdgePatternExpr> rightToLeftList = new ArrayList<>(workingEdgePatternList);
-                rightToLeftList.add(new EdgePatternExpr(workingLeftVertex, rightVertex, rightToLeftEdgeDescriptor));
-                visitedEdgeLists.add(rightToLeftList);
-            }
-            decompositionQueue.addAll(visitedEdgeLists);
-
-            // Build the associated path record (for our environment to hold).
-            pathEnumerationEnvironment.buildPathRecord(workingLeftVertex.getVariableExpr(), graphEdgeVar,
-                    rightVertex.getVariableExpr());
-
-            // Move our "vertex cursor".
-            workingLeftVertex = rightVertex;
-        }
-
-        // Publish our isomorphism and rebindings.
-        for (List<EdgePatternExpr> ignored : decompositionQueue) {
-            pathEnumerationEnvironment.publishIsomorphism();
-            pathEnumerationEnvironment.publishRebindings();
-        }
-        return new ArrayList<>(decompositionQueue);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedVarPathExpansion.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedVarPathExpansion.java
deleted file mode 100644
index 73a9954..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedVarPathExpansion.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.expand;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-
-/**
- * Given a path of variable length, there are {@link EdgeDescriptor#getMaximumHops()} -
- * {@link EdgeDescriptor#getMinimumHops()} valid paths that could appear in our result. Generate a fixed sub-path for
- * each of the aforementioned paths and decompose according to {@link UndirectedFixedPathExpansion}.
- *
- * @see UndirectedFixedPathExpansion
- */
-public class UndirectedVarPathExpansion implements IEdgePatternExpansion {
-    private final PathEnumerationEnvironment pathEnumerationEnvironment;
-
-    public UndirectedVarPathExpansion(PathEnumerationEnvironment pathEnumerationEnvironment) {
-        this.pathEnumerationEnvironment = pathEnumerationEnvironment;
-    }
-
-    @Override
-    public Iterable<Iterable<EdgePatternExpr>> expand(EdgePatternExpr edgePatternExpr) {
-        // Ensure we have been given the correct type of sub-path.
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        if (edgeDescriptor.getPatternType() != EdgeDescriptor.PatternType.PATH
-                || edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED
-                || Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-            throw new IllegalArgumentException("Expected an undirected var sub-path, but given a " + edgeDescriptor);
-        }
-
-        List<List<EdgePatternExpr>> decomposedEdgeList = new ArrayList<>();
-        for (int i = edgeDescriptor.getMinimumHops(); i <= edgeDescriptor.getMaximumHops(); i++) {
-            EdgeDescriptor fixedEdgeDescriptor =
-                    new EdgeDescriptor(EdgeDescriptor.EdgeDirection.UNDIRECTED, EdgeDescriptor.PatternType.PATH,
-                            edgeDescriptor.getEdgeLabels(), edgeDescriptor.getVariableExpr(), i, i);
-            EdgePatternExpr fixedEdgePattern = new EdgePatternExpr(edgePatternExpr.getLeftVertex(),
-                    edgePatternExpr.getRightVertex(), fixedEdgeDescriptor);
-            fixedEdgePattern.replaceInternalVertices(edgePatternExpr.getInternalVertices());
-
-            // We defer the decomposition of each individual path to UndirectedFixedPathExpansion.
-            new UndirectedFixedPathExpansion(pathEnumerationEnvironment).expand(fixedEdgePattern).forEach(e -> {
-                List<EdgePatternExpr> decompositionIntermediate = new ArrayList<>();
-                e.forEach(decompositionIntermediate::add);
-                decomposedEdgeList.add(decompositionIntermediate);
-            });
-        }
-
-        return new ArrayList<>(decomposedEdgeList);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java
new file mode 100644
index 0000000..ebb1406
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/EnvironmentActionFactory.java
@@ -0,0 +1,537 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower;
+
+import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
+import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildVertexEdgeJoin;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.AbstractInlineAction;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.IEnvironmentAction;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.PathPatternAction;
+import org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.TrueLiteral;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+
+/**
+ * Build {@link IEnvironmentAction} instances to manipulate a {@link LoweringEnvironment}.
+ */
+public class EnvironmentActionFactory {
+    private final Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
+    private final ElementLookupTable elementLookupTable;
+    private final LoweringAliasLookupTable aliasLookupTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+
+    // The following must be provided before any creation methods are used.
+    private GraphIdentifier graphIdentifier;
+
+    public EnvironmentActionFactory(Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap,
+            ElementLookupTable elementLookupTable, LoweringAliasLookupTable aliasLookupTable,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.analysisContextMap = analysisContextMap;
+        this.elementLookupTable = elementLookupTable;
+        this.aliasLookupTable = aliasLookupTable;
+        this.graphixRewritingContext = graphixRewritingContext;
+    }
+
+    public void reset(GraphIdentifier graphIdentifier) {
+        this.graphIdentifier = graphIdentifier;
+        this.aliasLookupTable.reset();
+    }
+
+    /**
+     * @see PathPatternAction
+     */
+    public IEnvironmentAction buildPathPatternAction(PathPatternExpr pathPatternExpr) {
+        return new PathPatternAction(pathPatternExpr);
+    }
+
+    /**
+     * @see IsomorphismAction
+     */
+    public IEnvironmentAction buildIsomorphismAction(FromGraphClause fromGraphClause) throws CompilationException {
+        return new IsomorphismAction(graphixRewritingContext, fromGraphClause, aliasLookupTable);
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle a dangling vertex / vertex that is (currently) disconnected.
+     * Even though we introduce CROSS-JOINs here, we will not actually perform this CROSS-JOIN if this is the first
+     * vertex we are lowering. There are three possible {@link IEnvironmentAction}s generated here:
+     * 1. An action for inlined vertices that have no projections.
+     * 2. An action for inlined vertices with projections.
+     * 3. An action for non-inlined vertices.
+     */
+    public IEnvironmentAction buildDanglingVertexAction(VertexPatternExpr vertexPatternExpr)
+            throws CompilationException {
+        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
+        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
+        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
+
+        // We should only be working with one identifier (given that we only have one label).
+        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
+        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Introduce our iteration expression.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // Bind our intermediate (join) variable and vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
+                    });
+                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else if (vertexAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Introduce our iteration expression.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        CallExpr datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // Build a record constructor from our context to bind to our vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeVertexBinding(vertexVar, recordConstructor2);
+                    });
+                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
+            return loweringEnvironment -> {
+                // Introduce our iteration expression.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
+                            new VariableExpr(iterationVar), null, new LiteralExpr(TrueLiteral.INSTANCE), null);
+                    clauseSequence.addMainClause(joinClause);
+                });
+
+                // Bind our intermediate (join) variable and vertex variable.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                    clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
+                });
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle an edge that we can fold into (attach from) an already introduced
+     * vertex. A folded edge is implicitly inlined. There are two possible {@link IEnvironmentAction}s generated here:
+     * 1. An action for inlined, folded edges that have no projections.
+     * 2. An action for inlined, folded edges that have projections.
+     */
+    public IEnvironmentAction buildFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr) throws CompilationException {
+        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
+        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
+        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
+
+        // We should only be working with one identifier (given that we only have one label).
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        if (edgeAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
+                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // Build a binding for our edge variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr elementVarExpr = new VariableExpr(elementVariable);
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        clauseSequence.addMainClause(new CorrLetClause(elementVarExpr, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(elementVariable));
+                    });
+                    aliasLookupTable.putIterationAlias(edgeVar, elementVariable);
+                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, null) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // We want to bind directly to the iteration variable of our vertex, not the join variable.
+                    elementVariable = aliasLookupTable.getIterationAlias(vertexVar);
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // Build a record constructor from our context to bind to our edge and intermediate (join) var.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, recordConstructor2);
+                    });
+                    aliasLookupTable.putIterationAlias(edgeVar, elementVariable);
+                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle an edge that we cannot fold into an already introduced vertex.
+     * There are three possible {@link IEnvironmentAction}s generated here:
+     * 1. An action for inlined edges that have no projections.
+     * 2. An action for inlined edges that have projections.
+     * 3. An action for non-inlined edges.
+     */
+    public IEnvironmentAction buildNonFoldedEdgeAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
+        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
+        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
+        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
+
+        // We should only be working with one edge identifier...
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        Expression datasetCallExpression = edgeAnalysisContext.getDatasetCallExpression();
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        if (edgeAnalysisContext.isExpressionInline() && edgeAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our edge iteration variable to our vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // Bind our intermediate (join) variable and edge variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(iterationVar));
+                    });
+                    aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else if (edgeAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, edgeAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our edge iteration variable to our vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our edge body.
+                    super.apply(loweringEnvironment);
+
+                    // Build a record constructor from our context to bind to our edge variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeEdgeBinding(edgeVar, recordConstructor2);
+                    });
+                    aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(edgeIdentifier);
+            return loweringEnvironment -> {
+                // Join our edge body to our vertex variable.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
+                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                            buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                            buildAccessorList(new VariableExpr(iterationVar), edgeKeyAccess.apply(edgeIdentifier)));
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
+                            new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                    clauseSequence.addMainClause(joinClause);
+                });
+
+                // Bind our intermediate (join) variable and edge variable.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                    clauseSequence.addRepresentativeEdgeBinding(edgeVar, new VariableExpr(iterationVar));
+                });
+                aliasLookupTable.putIterationAlias(edgeVar, iterationVar);
+                aliasLookupTable.putJoinAlias(edgeVar, intermediateVar);
+            };
+        }
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to introduce a WHERE-CLAUSE that will correlate a vertex and edge.
+     */
+    public IEnvironmentAction buildRawJoinVertexAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
+        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
+
+        // We should only be working with one edge identifier...
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        return loweringEnvironment -> {
+            // No aliases need to be introduced, we just need to add a WHERE-CONJUNCT.
+            VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
+            VariableExpr vertexJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(vertexVar));
+            loweringEnvironment.acceptTransformer(clauseSequence -> {
+                Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                        buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                        buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
+                clauseSequence.addMainClause(new CorrWhereClause(vertexEdgeJoin));
+            });
+        };
+    }
+
+    /**
+     * Build an {@link IEnvironmentAction} to handle a vertex that is bound to an existing (already introduced) edge.
+     * There are three possible {@link IEnvironmentAction}s generated here:
+     * 1. An action for inlined vertices that have no projections.
+     * 2. An action for inlined vertices that have projections.
+     * 3. An action for non-inlined vertices.
+     */
+    public IEnvironmentAction buildBoundVertexAction(VertexPatternExpr vertexPatternExpr,
+            EdgePatternExpr edgePatternExpr, Function<GraphElementIdentifier, List<List<String>>> edgeKeyAccess)
+            throws CompilationException {
+        VarIdentifier edgeVar = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
+        VarIdentifier vertexVar = vertexPatternExpr.getVariableExpr().getVar();
+        VarIdentifier iterationVar = graphixRewritingContext.getNewGraphixVariable();
+        VarIdentifier intermediateVar = graphixRewritingContext.getNewGraphixVariable();
+
+        // We should only be working with one edge identifier...
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
+
+        // ...and only one vertex identifier (given that we only have one label).
+        List<GraphElementIdentifier> vertexElementIDs = vertexPatternExpr.generateIdentifiers(graphIdentifier);
+        if (vertexElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical vertex pattern!");
+        }
+        GraphElementIdentifier vertexIdentifier = vertexElementIDs.get(0);
+        ElementBodyAnalysisContext vertexAnalysisContext = analysisContextMap.get(vertexIdentifier);
+        Expression datasetCallExpression = vertexAnalysisContext.getDatasetCallExpression();
+        if (vertexAnalysisContext.isExpressionInline() && vertexAnalysisContext.isSelectClauseInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our vertex iteration variable to our edge variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
+                        VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // Bind our intermediate (join) variable and vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
+                    });
+                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else if (vertexAnalysisContext.isExpressionInline()) {
+            return new AbstractInlineAction(graphixRewritingContext, vertexAnalysisContext, iterationVar) {
+                @Override
+                public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+                    // Join our vertex iteration variable to our edge variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
+                        VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
+                        Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                                buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                                buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
+                        JoinClause joinClause = new JoinClause(JoinType.INNER, datasetCallExpression,
+                                new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                        clauseSequence.addMainClause(joinClause);
+                    });
+
+                    // Inline our vertex body.
+                    super.apply(loweringEnvironment);
+
+                    // Build a record constructor from our context to bind to our vertex variable.
+                    loweringEnvironment.acceptTransformer(clauseSequence -> {
+                        VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                        RecordConstructor recordConstructor1 = buildRecordConstructor();
+                        RecordConstructor recordConstructor2 = buildRecordConstructor();
+                        clauseSequence.addMainClause(new CorrLetClause(recordConstructor1, intermediateVarExpr, null));
+                        clauseSequence.addRepresentativeVertexBinding(vertexVar, recordConstructor2);
+                    });
+                    aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
+                    aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
+                }
+            };
+
+        } else {
+            GraphElementDeclaration elementDeclaration = elementLookupTable.getElementDecl(vertexIdentifier);
+            return loweringEnvironment -> {
+                // Join our vertex body to our edge variable.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    VariableExpr edgeJoinExpr = new VariableExpr(aliasLookupTable.getJoinAlias(edgeVar));
+                    VariableExpr vertexJoinExpr = new VariableExpr(iterationVar);
+                    Expression vertexEdgeJoin = buildVertexEdgeJoin(
+                            buildAccessorList(vertexJoinExpr, elementLookupTable.getVertexKey(vertexIdentifier)),
+                            buildAccessorList(edgeJoinExpr, edgeKeyAccess.apply(edgeIdentifier)));
+                    JoinClause joinClause = new JoinClause(JoinType.INNER, elementDeclaration.getNormalizedBody(),
+                            new VariableExpr(iterationVar), null, vertexEdgeJoin, null);
+                    clauseSequence.addMainClause(joinClause);
+                });
+
+                // Bind our intermediate (join) variable and vertex variable.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    VariableExpr iterationVarExpr = new VariableExpr(iterationVar);
+                    VariableExpr intermediateVarExpr = new VariableExpr(intermediateVar);
+                    clauseSequence.addMainClause(new CorrLetClause(iterationVarExpr, intermediateVarExpr, null));
+                    clauseSequence.addRepresentativeVertexBinding(vertexVar, new VariableExpr(iterationVar));
+                });
+                aliasLookupTable.putIterationAlias(vertexVar, iterationVar);
+                aliasLookupTable.putJoinAlias(vertexVar, intermediateVar);
+            };
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/GraphixLowerSupplier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/GraphixLowerSupplier.java
deleted file mode 100644
index 8433a9f..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/GraphixLowerSupplier.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.BinaryOperator;
-import java.util.function.Consumer;
-import java.util.function.Function;
-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.PathPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.assembly.ExprAssembler;
-import org.apache.asterix.graphix.lang.rewrites.assembly.IExprAssembly;
-import org.apache.asterix.graphix.lang.rewrites.common.EdgeDependencyGraph;
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.DanglingVertexLowerAssembly;
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.IsomorphismLowerAssembly;
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.NamedPathLowerAssembly;
-import org.apache.asterix.graphix.lang.rewrites.util.EdgeRewritingUtil;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.Literal;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.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.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-
-/**
- * Supplier for lower AST nodes (i.e. SQL++ AST nodes). We perform the following process:
- * 1. Given the edge-dependency graph, fetch all supplied orderings of {@link EdgePatternExpr}. We will create a
- * separate assembly branch for each ordering.
- * 2. Once each assembly branch has been built, construct a left-deep LEFT-OUTER-JOIN tree out of the tail of each
- * assembly. This will result in one representative "junction" {@link LowerSupplierNode} node.
- * 3. If there are dangling vertices, merge the junction node from #2 with a lower node that represents all dangling
- * vertices.
- * 4. Gather all projected vertices and edges and attach isomorphism conjuncts to the tail {@link LowerSupplierNode}
- * node. Unlike our previous steps, this does not produce a new lower node.
- * 5. If there are any named paths (i.e. not sub-paths within {@link EdgePatternExpr}}, modify the tail
- * {@link LowerSupplierNode} to define a LET-CLAUSE that binds the path variable, and a projection to output said
- * path variable. Similar to the previous step, this does not produce a new lower node.
- */
-public class GraphixLowerSupplier {
-    private final Function<EdgePatternExpr, IExprAssembly<LowerSupplierNode>> lowerStrategyNodeSupplier;
-    private final LowerSupplierContext lowerSupplierContext;
-    private final EdgeDependencyGraph edgeDependencyGraph;
-
-    public GraphixLowerSupplier(LowerSupplierContext lowerSupplierContext, EdgeDependencyGraph edgeDependencyGraph,
-            Function<EdgePatternExpr, IExprAssembly<LowerSupplierNode>> lowerStrategyNodeSupplier) {
-        this.edgeDependencyGraph = edgeDependencyGraph;
-        this.lowerSupplierContext = lowerSupplierContext;
-        this.lowerStrategyNodeSupplier = lowerStrategyNodeSupplier;
-    }
-
-    public void invoke(Consumer<LowerSupplierNode> lowerNodeConsumer) throws CompilationException {
-        // Handle each of our edge orderings. Additionally, hand off each generated LET-CLAUSE to our caller.
-        List<ExprAssembler<LowerSupplierNode>> edgeAssemblers = new ArrayList<>();
-        for (Iterable<EdgePatternExpr> edgeOrdering : edgeDependencyGraph) {
-            ExprAssembler<LowerSupplierNode> edgeAssembler = new ExprAssembler<>();
-            for (EdgePatternExpr edgePatternExpr : edgeOrdering) {
-                edgeAssembler.bind(lowerStrategyNodeSupplier.apply(edgePatternExpr));
-            }
-            edgeAssembler.iterator().forEachRemaining(lowerNodeConsumer);
-            edgeAssemblers.add(edgeAssembler);
-        }
-
-        // Join each parallel assembly with a LEFT-OUTER-JOIN.
-        LowerSupplierNode tailLowerNode = edgeAssemblers.stream().sequential().map(ExprAssembler::getLast)
-                .reduce(buildLowerNodeMerger(JoinType.LEFTOUTER, lowerNodeConsumer)).orElse(null);
-
-        // If dangling vertices exist, merge the previous node with the dangling-vertex assembly.
-        if (!lowerSupplierContext.getDanglingVertices().isEmpty()) {
-            ExprAssembler<LowerSupplierNode> danglingVertexAssembler = new ExprAssembler<>();
-            danglingVertexAssembler.bind(new DanglingVertexLowerAssembly(lowerSupplierContext));
-            lowerNodeConsumer.accept(danglingVertexAssembler.getLast());
-
-            // Connect this assembler to the edge assembly tail.
-            if (tailLowerNode != null) {
-                tailLowerNode = buildLowerNodeMerger(JoinType.INNER, lowerNodeConsumer).apply(tailLowerNode,
-                        danglingVertexAssembler.getLast());
-
-            } else {
-                tailLowerNode = danglingVertexAssembler.getLast();
-            }
-        }
-
-        // Attach our isomorphism conjuncts (and disjuncts) to the tail node (if necessary).
-        IsomorphismLowerAssembly isomorphismLowerAssembly = new IsomorphismLowerAssembly(
-                lowerSupplierContext.getDanglingVertices(), lowerSupplierContext.getOptionalVariables(),
-                edgeDependencyGraph, lowerSupplierContext.getMetadataProvider());
-        tailLowerNode = isomorphismLowerAssembly.apply(Objects.requireNonNull(tailLowerNode));
-
-        // If we have any named paths (i.e. not a sub-path in an EDGE-PATTERN-EXPR), add them to the tail lower node.
-        List<PathPatternExpr> pathPatternExprList = lowerSupplierContext.getPathPatternExprList();
-        NamedPathLowerAssembly namedPathLowerAssembly = new NamedPathLowerAssembly(pathPatternExprList);
-        namedPathLowerAssembly.apply(tailLowerNode);
-    }
-
-    private BinaryOperator<LowerSupplierNode> buildLowerNodeMerger(JoinType joinType,
-            Consumer<LowerSupplierNode> mergerCallbackConsumer) {
-        return (n1, n2) -> {
-            // Build a reference to our N1 node.
-            VariableExpr n1BindingVar = n1.getBindingVar();
-            VariableExpr n1ReferenceVar = new VariableExpr(lowerSupplierContext.getNewVariable());
-
-            // Build a reference to our N2 node.
-            VariableExpr n2BindingVar = n2.getBindingVar();
-            VariableExpr n2ReferenceVar = new VariableExpr(lowerSupplierContext.getNewVariable());
-
-            // Build the JOIN-CLAUSE, and add the reference to N2 in a correlated clause to a FROM-TERM of N1.
-            Expression whereConjunct = null;
-            for (Projection n2Projection : n2.getProjectionList()) {
-                Expression toProjectionExpr = new FieldAccessor(n2ReferenceVar, new Identifier(n2Projection.getName()));
-                whereConjunct = EdgeRewritingUtil.buildInputAssemblyJoin(n1, n1ReferenceVar, toProjectionExpr,
-                        whereConjunct, n -> n.equals(n2Projection.getName()));
-            }
-            List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<>();
-            correlateClauses.add(new JoinClause(joinType, n2BindingVar, n2ReferenceVar, null, whereConjunct,
-                    (joinType == JoinType.INNER) ? null : Literal.Type.MISSING));
-            FromTerm fromTerm = new FromTerm(n1BindingVar, n1ReferenceVar, null, correlateClauses);
-
-            // Combine the projection list from N1 and N2. Avoid duplicates.
-            List<Projection> projectionList = n1.getProjectionList().stream().map(p -> {
-                String n1VarName = p.getName();
-                VariableExpr n1ProjectionVar = new VariableExpr(new VarIdentifier(n1VarName));
-                FieldAccessor n1ProjectionAccess = new FieldAccessor(n1ReferenceVar, n1ProjectionVar.getVar());
-                return new Projection(Projection.Kind.NAMED_EXPR, n1ProjectionAccess, n1VarName);
-            }).collect(Collectors.toList());
-            for (Projection n2Projection : n2.getProjectionList()) {
-                String n2VarName = n2Projection.getName();
-                if (projectionList.stream().map(Projection::getName).noneMatch(n2VarName::equals)) {
-                    VariableExpr n2ProjectionVar = new VariableExpr(new VarIdentifier(n2VarName));
-                    FieldAccessor n2ProjectionAccess = new FieldAccessor(n2ReferenceVar, n2ProjectionVar.getVar());
-                    projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, n2ProjectionAccess, n2VarName));
-                }
-            }
-
-            // Assemble the new lowering node.
-            LowerSupplierNode outputLowerSupplierNode = new LowerSupplierNode(Collections.singletonList(fromTerm), null,
-                    projectionList, new VariableExpr(lowerSupplierContext.getNewVariable()));
-            mergerCallbackConsumer.accept(outputLowerSupplierNode);
-            return outputLowerSupplierNode;
-        };
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierContext.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierContext.java
deleted file mode 100644
index 1c24588..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierContext.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower;
-
-import java.util.List;
-
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-
-public class LowerSupplierContext {
-    private final GraphixRewritingContext graphixRewritingContext;
-    private final GraphIdentifier graphIdentifier;
-    private final List<VertexPatternExpr> danglingVertices;
-    private final List<VarIdentifier> optionalVariables;
-    private final List<PathPatternExpr> pathPatternExprList;
-    private final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
-
-    public LowerSupplierContext(GraphixRewritingContext graphixRewritingContext, GraphIdentifier graphIdentifier,
-            List<VertexPatternExpr> danglingVertices, List<VarIdentifier> optionalVariables,
-            List<PathPatternExpr> pathPatternExprList, ElementLookupTable<GraphElementIdentifier> elementLookupTable) {
-        this.elementLookupTable = elementLookupTable;
-        this.danglingVertices = danglingVertices;
-        this.optionalVariables = optionalVariables;
-        this.graphIdentifier = graphIdentifier;
-        this.pathPatternExprList = pathPatternExprList;
-        this.graphixRewritingContext = graphixRewritingContext;
-    }
-
-    public GraphIdentifier getGraphIdentifier() {
-        return graphIdentifier;
-    }
-
-    public ElementLookupTable<GraphElementIdentifier> getElementLookupTable() {
-        return elementLookupTable;
-    }
-
-    public List<VertexPatternExpr> getDanglingVertices() {
-        return danglingVertices;
-    }
-
-    public List<VarIdentifier> getOptionalVariables() {
-        return optionalVariables;
-    }
-
-    public MetadataProvider getMetadataProvider() {
-        return graphixRewritingContext.getMetadataProvider();
-    }
-
-    public VarIdentifier getNewVariable() {
-        return graphixRewritingContext.getNewVariable();
-    }
-
-    public List<PathPatternExpr> getPathPatternExprList() {
-        return pathPatternExprList;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierNode.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierNode.java
deleted file mode 100644
index 0d07ae2..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierNode.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildLetClauseWithFromList;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.AbstractLowerAssembly;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-
-/**
- * A helper class for building the {@link LetClause} that represents the final lowered representation of a query edge.
- * This is used as the node type for {@link AbstractLowerAssembly}.
- */
-public class LowerSupplierNode {
-    private final List<FromTerm> fromTermList;
-    private final List<AbstractClause> whereClauses;
-    private final List<Projection> projectionList;
-    private final VariableExpr bindingVar;
-
-    public LowerSupplierNode(List<FromTerm> fromTermList, List<AbstractClause> whereClauses,
-            List<Projection> projectionList, VariableExpr bindingVar) {
-        this.fromTermList = fromTermList;
-        this.projectionList = projectionList;
-        this.bindingVar = bindingVar;
-        this.whereClauses = (whereClauses == null) ? new ArrayList<>() : whereClauses;
-    }
-
-    public List<Projection> getProjectionList() {
-        return projectionList;
-    }
-
-    public VariableExpr getBindingVar() {
-        return bindingVar;
-    }
-
-    public List<AbstractClause> getWhereClauses() {
-        return whereClauses;
-    }
-
-    public LetClause buildLetClause() {
-        return buildLetClauseWithFromList(fromTermList, whereClauses, projectionList, bindingVar);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java
new file mode 100644
index 0000000..0642c20
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringAliasLookupTable.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+
+/**
+ * Lookup table for JOIN and ITERATION aliases, indexed by their representative (i.e. element) variable identifiers.
+ */
+public class LoweringAliasLookupTable {
+    private final Map<VarIdentifier, VarIdentifier> joinAliasMap = new HashMap<>();
+    private final Map<VarIdentifier, VarIdentifier> iterationAliasMap = new HashMap<>();
+
+    public void putJoinAlias(VarIdentifier elementVariable, VarIdentifier aliasVariable) {
+        joinAliasMap.put(elementVariable, aliasVariable);
+    }
+
+    public void putIterationAlias(VarIdentifier elementVariable, VarIdentifier aliasVariable) {
+        iterationAliasMap.put(elementVariable, aliasVariable);
+    }
+
+    public VarIdentifier getJoinAlias(VarIdentifier elementVariable) {
+        return joinAliasMap.get(elementVariable);
+    }
+
+    public VarIdentifier getIterationAlias(VarIdentifier elementVariable) {
+        return iterationAliasMap.get(elementVariable);
+    }
+
+    public void reset() {
+        joinAliasMap.clear();
+        iterationAliasMap.clear();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java
new file mode 100644
index 0000000..dc9f594
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LoweringEnvironment.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower;
+
+import static org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil.toUserDefinedVariableName;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.function.Consumer;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrites.lower.action.IEnvironmentAction;
+import org.apache.asterix.graphix.lang.rewrites.lower.transform.CorrelatedClauseSequence;
+import org.apache.asterix.graphix.lang.rewrites.lower.transform.ISequenceTransformer;
+import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.Literal;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.TrueLiteral;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.IteratorUtils;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
+import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.hyracks.api.exceptions.Warning;
+
+/**
+ * @see org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor
+ */
+public class LoweringEnvironment {
+    private final GraphSelectBlock graphSelectBlock;
+    private final CorrelatedClauseSequence mainClauseSequence;
+    private final GraphixRewritingContext graphixRewritingContext;
+    private CorrelatedClauseSequence leftMatchClauseSequence;
+
+    public LoweringEnvironment(GraphSelectBlock graphSelectBlock, GraphixRewritingContext graphixRewritingContext) {
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.mainClauseSequence = new CorrelatedClauseSequence();
+        this.graphSelectBlock = graphSelectBlock;
+        this.leftMatchClauseSequence = null;
+    }
+
+    public void acceptAction(IEnvironmentAction environmentAction) throws CompilationException {
+        environmentAction.apply(this);
+    }
+
+    public void acceptTransformer(ISequenceTransformer sequenceTransformer) throws CompilationException {
+        boolean isLeftMatch = leftMatchClauseSequence == null;
+        sequenceTransformer.accept(isLeftMatch ? mainClauseSequence : leftMatchClauseSequence);
+    }
+
+    public void beginLeftMatch() throws CompilationException {
+        if (leftMatchClauseSequence != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "LEFT-MATCH lowering is currently in progress!");
+        }
+        leftMatchClauseSequence = new CorrelatedClauseSequence();
+    }
+
+    public void endLeftMatch(IWarningCollector warningCollector) throws CompilationException {
+        VariableSubstitutionVisitor substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
+        VariableExpr nestingVariableExpr = new VariableExpr(graphixRewritingContext.getNewGraphixVariable());
+        final Consumer<VarIdentifier> substitutionAdder = v -> {
+            VariableExpr sourceNestingVariableExpr = new VariableExpr(nestingVariableExpr.getVar());
+            FieldAccessor fieldAccessor = new FieldAccessor(sourceNestingVariableExpr, v);
+            substitutionVisitor.addSubstitution(v, fieldAccessor);
+        };
+
+        // Build up our projection list.
+        List<Projection> projectionList = new ArrayList<>();
+        ListIterator<AbstractBinaryCorrelateClause> forProjectIterator = leftMatchClauseSequence.getMainIterator();
+        while (forProjectIterator.hasNext()) {
+            AbstractBinaryCorrelateClause workingClause = forProjectIterator.next();
+            if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                continue;
+            }
+            VarIdentifier rightVariable = workingClause.getRightVariable().getVar();
+            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, new VariableExpr(rightVariable),
+                    toUserDefinedVariableName(rightVariable).getValue()));
+            substitutionAdder.accept(rightVariable);
+        }
+
+        // Assemble a FROM-TERM from our LEFT-MATCH sequence (do not add our representatives).
+        JoinClause headCorrelateClause = (JoinClause) leftMatchClauseSequence.popMainClauseSequence();
+        ListIterator<AbstractBinaryCorrelateClause> mainIterator = leftMatchClauseSequence.getMainIterator();
+        List<AbstractBinaryCorrelateClause> correlateClauses = IteratorUtils.toList(mainIterator);
+        raiseCrossJoinWarning(correlateClauses, warningCollector, null);
+        FromTerm fromTerm = new FromTerm(headCorrelateClause.getRightExpression(),
+                headCorrelateClause.getRightVariable(), headCorrelateClause.getPositionalVariable(), correlateClauses);
+
+        // Nest our FROM-TERM.
+        SelectClause selectClause = new SelectClause(null, new SelectRegular(projectionList), false);
+        SelectBlock selectBlock = new SelectBlock(selectClause, new FromClause(List.of(fromTerm)), null, null, null);
+        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
+        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
+        SelectExpression selectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
+
+        // Attach our assembled sequence back to the main sequence.
+        Expression conditionExpression = headCorrelateClause.getConditionExpression();
+        Expression newConditionExpression = conditionExpression.accept(substitutionVisitor, null);
+        JoinClause leftJoinClause = new JoinClause(JoinType.LEFTOUTER, selectExpression, nestingVariableExpr, null,
+                newConditionExpression, Literal.Type.MISSING);
+        mainClauseSequence.addMainClause(leftJoinClause);
+
+        // Introduce our representative variables back into  our main sequence.
+        for (CorrLetClause representativeVertexBinding : leftMatchClauseSequence.getRepresentativeVertexBindings()) {
+            VarIdentifier representativeVariable = representativeVertexBinding.getRightVariable().getVar();
+            Expression rightExpression = representativeVertexBinding.getRightExpression();
+            Expression reboundExpression = rightExpression.accept(substitutionVisitor, null);
+            mainClauseSequence.addRepresentativeVertexBinding(representativeVariable, reboundExpression);
+        }
+        for (CorrLetClause representativeEdgeBinding : leftMatchClauseSequence.getRepresentativeEdgeBindings()) {
+            VarIdentifier representativeVariable = representativeEdgeBinding.getRightVariable().getVar();
+            Expression rightExpression = representativeEdgeBinding.getRightExpression();
+            Expression reboundExpression = rightExpression.accept(substitutionVisitor, null);
+            mainClauseSequence.addRepresentativeEdgeBinding(representativeVariable, reboundExpression);
+        }
+        leftMatchClauseSequence = null;
+    }
+
+    public void finalizeLowering(FromGraphClause fromGraphClause, IWarningCollector warningCollector) {
+        AbstractBinaryCorrelateClause headCorrelateClause = mainClauseSequence.popMainClauseSequence();
+        List<AbstractBinaryCorrelateClause> correlateClauses = IterableUtils.toList(mainClauseSequence);
+        raiseCrossJoinWarning(correlateClauses, warningCollector, fromGraphClause.getSourceLocation());
+        FromTerm outputFromTerm = new FromTerm(headCorrelateClause.getRightExpression(),
+                headCorrelateClause.getRightVariable(), headCorrelateClause.getPositionalVariable(), correlateClauses);
+        graphSelectBlock.setFromClause(new FromClause(List.of(outputFromTerm)));
+        graphSelectBlock.getFromClause().setSourceLocation(fromGraphClause.getSourceLocation());
+    }
+
+    private static void raiseCrossJoinWarning(List<AbstractBinaryCorrelateClause> correlateClauses,
+            IWarningCollector warningCollector, SourceLocation sourceLocation) {
+        for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
+            if (correlateClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
+                JoinClause joinClause = (JoinClause) correlateClause;
+                if (joinClause.getConditionExpression().getKind() == Expression.Kind.LITERAL_EXPRESSION) {
+                    LiteralExpr literalExpr = (LiteralExpr) joinClause.getConditionExpression();
+                    if (literalExpr.getValue().equals(TrueLiteral.INSTANCE) && warningCollector.shouldWarn()) {
+                        warningCollector.warn(Warning.of(sourceLocation, ErrorCode.COMPILATION_ERROR,
+                                "Potential disconnected pattern encountered! A CROSS-JOIN has been introduced."));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java
new file mode 100644
index 0000000..b82801b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/AbstractInlineAction.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower.action;
+
+import static org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.FieldBinding;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.StringLiteral;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.Projection;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+
+/**
+ * Inline an element body into a {@link LoweringEnvironment}. This includes a) copying {@link UnnestClause},
+ * {@link LetClause}, and {@link WhereClause} AST nodes from our body analysis, and b) creating
+ * {@link RecordConstructor} AST nodes to inline {@link org.apache.asterix.lang.sqlpp.clause.SelectRegular} nodes.
+ */
+public abstract class AbstractInlineAction implements IEnvironmentAction {
+    protected final GraphixRewritingContext graphixRewritingContext;
+    protected final ElementBodyAnalysisContext bodyAnalysisContext;
+
+    // This may be mutated by our child.
+    protected VarIdentifier elementVariable;
+
+    // The following is reset on each application.
+    private VariableSubstitutionVisitor substitutionVisitor;
+
+    protected AbstractInlineAction(GraphixRewritingContext graphixRewritingContext,
+            ElementBodyAnalysisContext bodyAnalysisContext, VarIdentifier elementVariable) {
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.bodyAnalysisContext = bodyAnalysisContext;
+        this.elementVariable = elementVariable;
+    }
+
+    @Override
+    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+        // To inline, we need to ensure that we substitute variables accordingly.
+        substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
+        if (bodyAnalysisContext.getFromTermVariable() != null) {
+            VariableExpr fromTermVariableExpr = bodyAnalysisContext.getFromTermVariable();
+            VariableExpr elementVariableExpr = new VariableExpr(elementVariable);
+            substitutionVisitor.addSubstitution(fromTermVariableExpr.getVar(), elementVariableExpr);
+        }
+
+        // If we have any UNNEST clauses, we need to add these.
+        if (bodyAnalysisContext.getUnnestClauses() != null) {
+            for (AbstractBinaryCorrelateClause unnestClause : bodyAnalysisContext.getUnnestClauses()) {
+                VarIdentifier reboundUnnestVariableID = graphixRewritingContext.getNewGraphixVariable();
+                VariableExpr reboundVariableExpr = new VariableExpr(reboundUnnestVariableID);
+
+                // Remap this UNNEST-CLAUSE to include our new variables.
+                loweringEnvironment.acceptTransformer(clauseSequence -> {
+                    UnnestClause copiedClause = (UnnestClause) SqlppRewriteUtil.deepCopy(unnestClause);
+                    copiedClause.accept(substitutionVisitor, null);
+                    VariableExpr substitutionVariableExpr = (copiedClause.hasPositionalVariable())
+                            ? copiedClause.getPositionalVariable() : copiedClause.getRightVariable();
+                    substitutionVisitor.addSubstitution(substitutionVariableExpr.getVar(), reboundVariableExpr);
+                    UnnestClause newUnnestClause = new UnnestClause(copiedClause.getUnnestType(),
+                            copiedClause.getRightExpression(), new VariableExpr(reboundUnnestVariableID), null,
+                            copiedClause.getOuterUnnestMissingValueType());
+                    clauseSequence.addMainClause(newUnnestClause);
+                });
+            }
+        }
+
+        // If we have any LET clauses, we need to substitute them in our WHERE and SELECT clauses.
+        if (bodyAnalysisContext.getLetClauses() != null) {
+            for (LetClause letClause : bodyAnalysisContext.getLetClauses()) {
+                VarIdentifier reboundLetVariableID = graphixRewritingContext.getNewGraphixVariable();
+                VariableExpr reboundVariableExpr = new VariableExpr(reboundLetVariableID);
+
+                // Remap this LET-CLAUSE to include our new variables. Move this to our correlated clauses.
+                LetClause copiedClause = (LetClause) SqlppRewriteUtil.deepCopy(letClause);
+                copiedClause.accept(substitutionVisitor, null);
+                Expression copiedBindingExpr = copiedClause.getBindingExpr();
+                substitutionVisitor.addSubstitution(copiedClause.getVarExpr().getVar(), reboundVariableExpr);
+                loweringEnvironment.acceptTransformer(corrSequence -> {
+                    VariableExpr reboundLetVariableExpr = new VariableExpr(reboundLetVariableID);
+                    corrSequence.addMainClause(new CorrLetClause(copiedBindingExpr, reboundLetVariableExpr, null));
+                });
+            }
+        }
+
+        // If we have any WHERE clauses, we need to add these.
+        if (bodyAnalysisContext.getWhereClauses() != null) {
+            for (WhereClause whereClause : bodyAnalysisContext.getWhereClauses()) {
+                WhereClause copiedClause = (WhereClause) SqlppRewriteUtil.deepCopy(whereClause);
+                copiedClause.accept(substitutionVisitor, null);
+                loweringEnvironment.acceptTransformer(corrSequence -> {
+                    CorrWhereClause corrWhereClause = new CorrWhereClause(copiedClause.getWhereExpr());
+                    corrSequence.addMainClause(corrWhereClause);
+                });
+            }
+        }
+    }
+
+    protected RecordConstructor buildRecordConstructor() throws CompilationException {
+        if (bodyAnalysisContext.getSelectClauseProjections() != null) {
+            // Map our original variable to our element variable.
+            List<FieldBinding> fieldBindings = new ArrayList<>();
+            for (Projection projection : bodyAnalysisContext.getSelectClauseProjections()) {
+                LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(projection.getName()));
+                ILangExpression copiedExpr = SqlppRewriteUtil.deepCopy(projection.getExpression());
+                Expression fieldValueExpr = copiedExpr.accept(substitutionVisitor, null);
+                fieldBindings.add(new FieldBinding(fieldNameExpr, fieldValueExpr));
+            }
+            return new RecordConstructor(fieldBindings);
+
+        } else {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Non-inlineable SELECT clause encountered, but was body was marked as inline!");
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java
similarity index 77%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java
index 25efe98..4ab6030 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IEnvironmentAction.java
@@ -16,14 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+package org.apache.asterix.graphix.lang.rewrites.lower.action;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
 
-/**
- * @see ExprAssembler
- */
 @FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public interface IEnvironmentAction {
+    void apply(LoweringEnvironment loweringEnvironment) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java
new file mode 100644
index 0000000..e54cc74
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/IsomorphismAction.java
@@ -0,0 +1,306 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower.action;
+
+import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.COMPLETE_ISOMORPHISM;
+import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.EDGE_ISOMORPHISM;
+import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.HOMOMORPHISM;
+import static org.apache.asterix.graphix.lang.rewrites.lower.action.IsomorphismAction.MatchEvaluationKind.VERTEX_ISOMORPHISM;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
+import org.apache.asterix.graphix.lang.clause.CorrWhereClause;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringAliasLookupTable;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrites.lower.transform.CorrelatedClauseSequence;
+import org.apache.asterix.graphix.lang.rewrites.lower.transform.ISequenceTransformer;
+import org.apache.asterix.graphix.lang.rewrites.visitor.AbstractGraphixQueryVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.VariableSubstitutionVisitor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.JoinType;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+
+/**
+ * Define which graph elements are *not* equal to each other. We assume that all elements are named at this point and
+ * that our {@link FromGraphClause} is in canonical form. We enforce the following (by default, we enforce total
+ * isomorphism):
+ * 1. No vertex and no edge can appear more than once across all patterns of all {@link MatchClause} nodes.
+ * 2. For vertex-isomorphism, we enforce that no vertex can appear more than once across all {@link MatchClause} nodes.
+ * 3. For edge-isomorphism, we enforce that no edge can appear more than once across all {@link MatchClause} nodes.
+ * 4. For homomorphism, we enforce nothing. Edge adjacency is already implicitly preserved.
+ */
+public class IsomorphismAction implements IEnvironmentAction {
+    public enum MatchEvaluationKind {
+        COMPLETE_ISOMORPHISM("isomorphism"),
+        VERTEX_ISOMORPHISM("vertex-isomorphism"),
+        EDGE_ISOMORPHISM("edge-isomorphism"),
+        HOMOMORPHISM("homomorphism");
+
+        // The user specifies the options above through "SET `graphix.match-evaluation` '...';"
+        private final String metadataConfigOptionName;
+
+        MatchEvaluationKind(String metadataConfigOptionName) {
+            this.metadataConfigOptionName = metadataConfigOptionName;
+        }
+
+        @Override
+        public String toString() {
+            return metadataConfigOptionName;
+        }
+    }
+
+    // We will walk through our FROM-GRAPH-CLAUSE and determine our isomorphism conjuncts.
+    private final MatchEvaluationKind matchEvaluationKind;
+    private final FromGraphClause fromGraphClause;
+    private final LoweringAliasLookupTable aliasLookupTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+
+    public IsomorphismAction(GraphixRewritingContext graphixRewritingContext, FromGraphClause fromGraphClause,
+            LoweringAliasLookupTable aliasLookupTable) throws CompilationException {
+        final String metadataConfigKeyName = GraphixCompilationProvider.MATCH_EVALUATION_METADATA_CONFIG;
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.fromGraphClause = fromGraphClause;
+        this.aliasLookupTable = aliasLookupTable;
+
+        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+        if (metadataProvider.getConfig().containsKey(metadataConfigKeyName)) {
+            String metadataConfigKeyValue = (String) metadataProvider.getConfig().get(metadataConfigKeyName);
+            if (metadataConfigKeyValue.equalsIgnoreCase(COMPLETE_ISOMORPHISM.toString())) {
+                this.matchEvaluationKind = COMPLETE_ISOMORPHISM;
+
+            } else if (metadataConfigKeyValue.equalsIgnoreCase(VERTEX_ISOMORPHISM.toString())) {
+                this.matchEvaluationKind = VERTEX_ISOMORPHISM;
+
+            } else if (metadataConfigKeyValue.equalsIgnoreCase(EDGE_ISOMORPHISM.toString())) {
+                this.matchEvaluationKind = EDGE_ISOMORPHISM;
+
+            } else if (metadataConfigKeyValue.equalsIgnoreCase(HOMOMORPHISM.toString())) {
+                this.matchEvaluationKind = HOMOMORPHISM;
+
+            } else {
+                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, metadataConfigKeyValue);
+            }
+
+        } else {
+            this.matchEvaluationKind = COMPLETE_ISOMORPHISM;
+        }
+    }
+
+    private static List<OperatorExpr> generateIsomorphismConjuncts(List<VariableExpr> variableList) {
+        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
+
+        // Find all unique pairs from our list of variables.
+        for (int i = 0; i < variableList.size(); i++) {
+            for (int j = i + 1; j < variableList.size(); j++) {
+                OperatorExpr inequalityConjunct = new OperatorExpr();
+                inequalityConjunct.addOperator(OperatorType.NEQ);
+                inequalityConjunct.addOperand(variableList.get(i));
+                inequalityConjunct.addOperand(variableList.get(j));
+                isomorphismConjuncts.add(inequalityConjunct);
+            }
+        }
+
+        return isomorphismConjuncts;
+    }
+
+    @Override
+    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+        Map<ElementLabel, List<VariableExpr>> vertexVariableMap = new HashMap<>();
+        Map<ElementLabel, List<VariableExpr>> edgeVariableMap = new HashMap<>();
+
+        // Populate the collections above.
+        fromGraphClause.accept(new AbstractGraphixQueryVisitor() {
+            private void populateVariableMap(ElementLabel label, VariableExpr variableExpr,
+                    Map<ElementLabel, List<VariableExpr>> labelVariableMap) {
+                Function<VariableExpr, Boolean> mapMatchFinder = v -> {
+                    final String variableName = SqlppVariableUtil.toUserDefinedName(variableExpr.getVar().getValue());
+                    String inputVariableName = SqlppVariableUtil.toUserDefinedName(v.getVar().getValue());
+                    return variableName.equals(inputVariableName);
+                };
+                if (labelVariableMap.containsKey(label)) {
+                    if (labelVariableMap.get(label).stream().noneMatch(mapMatchFinder::apply)) {
+                        labelVariableMap.get(label).add(variableExpr);
+                    }
+
+                } else {
+                    List<VariableExpr> variableList = new ArrayList<>();
+                    variableList.add(variableExpr);
+                    labelVariableMap.put(label, variableList);
+                }
+            }
+
+            @Override
+            public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+                // We only want to explore the top level of our FROM-GRAPH-CLAUSEs.
+                for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+                    matchClause.accept(this, arg);
+                }
+                return null;
+            }
+
+            @Override
+            public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
+                VarIdentifier vertexVariableID = vertexPatternExpr.getVariableExpr().getVar();
+                VarIdentifier iterationVariableID = aliasLookupTable.getIterationAlias(vertexVariableID);
+                ElementLabel elementLabel = vertexPatternExpr.getLabels().iterator().next();
+                populateVariableMap(elementLabel, new VariableExpr(iterationVariableID), vertexVariableMap);
+                return null;
+            }
+
+            @Override
+            public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) {
+                VarIdentifier edgeVariableID = edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar();
+                VarIdentifier iterationVariableID = aliasLookupTable.getIterationAlias(edgeVariableID);
+                ElementLabel elementLabel = edgePatternExpr.getEdgeDescriptor().getEdgeLabels().iterator().next();
+                populateVariableMap(elementLabel, new VariableExpr(iterationVariableID), edgeVariableMap);
+                return null;
+            }
+        }, null);
+
+        // Construct our isomorphism conjuncts.
+        List<OperatorExpr> isomorphismConjuncts = new ArrayList<>();
+        if (matchEvaluationKind == COMPLETE_ISOMORPHISM || matchEvaluationKind == VERTEX_ISOMORPHISM) {
+            vertexVariableMap.values().stream().map(IsomorphismAction::generateIsomorphismConjuncts)
+                    .forEach(isomorphismConjuncts::addAll);
+        }
+        if (matchEvaluationKind == COMPLETE_ISOMORPHISM || matchEvaluationKind == EDGE_ISOMORPHISM) {
+            edgeVariableMap.values().stream().map(IsomorphismAction::generateIsomorphismConjuncts)
+                    .forEach(isomorphismConjuncts::addAll);
+        }
+
+        // Iterate through our clause sequence, and introduce our isomorphism conjuncts eagerly.
+        VariableSubstitutionVisitor substitutionVisitor = new VariableSubstitutionVisitor(graphixRewritingContext);
+        loweringEnvironment.acceptTransformer(new ISequenceTransformer() {
+            private final Set<VarIdentifier> visitedVariables = new HashSet<>();
+
+            private void acceptLeftMatchJoin(ListIterator<AbstractBinaryCorrelateClause> clauseIterator,
+                    JoinClause joinClause) throws CompilationException {
+                // We can make the following assumptions about our JOIN here (i.e. the casts here are valid).
+                Expression rightExpression = joinClause.getRightExpression();
+                SelectExpression selectExpression = (SelectExpression) rightExpression;
+                SelectSetOperation selectSetOperation = selectExpression.getSelectSetOperation();
+                SelectBlock selectBlock = selectSetOperation.getLeftInput().getSelectBlock();
+                FromTerm fromTerm = selectBlock.getFromClause().getFromTerms().get(0);
+                for (AbstractBinaryCorrelateClause workingClause : fromTerm.getCorrelateClauses()) {
+                    if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                        continue;
+                    }
+                    VarIdentifier rightVariable = workingClause.getRightVariable().getVar();
+
+                    // Add our isomorphism conjunct to our main iterator.
+                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
+                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
+                        List<Expression> operandList = isomorphismConjunct.getExprList();
+                        VarIdentifier termVariable1 = ((VariableExpr) operandList.get(0)).getVar();
+                        VarIdentifier termVariable2 = ((VariableExpr) operandList.get(1)).getVar();
+                        if (!termVariable1.equals(rightVariable) && !termVariable2.equals(rightVariable)) {
+                            continue;
+                        }
+
+                        // Add a substitution for our right variable.
+                        VariableExpr nestingVariableExpr1 = new VariableExpr(joinClause.getRightVariable().getVar());
+                        VariableExpr nestingVariableExpr2 = new VariableExpr(joinClause.getRightVariable().getVar());
+                        FieldAccessor fieldAccessor1 = new FieldAccessor(nestingVariableExpr1, rightVariable);
+                        FieldAccessor fieldAccessor2 = new FieldAccessor(nestingVariableExpr2, rightVariable);
+                        substitutionVisitor.addSubstitution(rightVariable, fieldAccessor1);
+                        Expression qualifiedConjunct = substitutionVisitor.visit(isomorphismConjunct, null);
+
+                        // Our right variable can also be optional.
+                        FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.IS_MISSING);
+                        CallExpr isMissingCallExpr = new CallExpr(functionSignature, List.of(fieldAccessor2));
+                        OperatorExpr disjunctionExpr = new OperatorExpr();
+                        disjunctionExpr.addOperator(OperatorType.OR);
+                        disjunctionExpr.addOperand(isMissingCallExpr);
+                        disjunctionExpr.addOperand(qualifiedConjunct);
+                        clauseIterator.add(new CorrWhereClause(disjunctionExpr));
+                        appliedIsomorphismConjuncts.add(isomorphismConjunct);
+                        visitedVariables.add(rightVariable);
+                    }
+                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
+                }
+            }
+
+            @Override
+            public void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException {
+                ListIterator<AbstractBinaryCorrelateClause> clauseIterator = clauseSequence.getMainIterator();
+                while (clauseIterator.hasNext()) {
+                    AbstractBinaryCorrelateClause workingClause = clauseIterator.next();
+                    if (workingClause.getClauseType() == Clause.ClauseType.WHERE_CLAUSE) {
+                        continue;
+                    }
+                    visitedVariables.add(workingClause.getRightVariable().getVar());
+
+                    // If we encounter a LEFT-JOIN, then we have created a LEFT-MATCH branch.
+                    if (workingClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
+                        JoinClause joinClause = (JoinClause) workingClause;
+                        if (joinClause.getJoinType() == JoinType.LEFTOUTER) {
+                            acceptLeftMatchJoin(clauseIterator, joinClause);
+                        }
+                    }
+
+                    // Only introduce our conjunct if we have visited both variables.
+                    Set<OperatorExpr> appliedIsomorphismConjuncts = new HashSet<>();
+                    for (OperatorExpr isomorphismConjunct : isomorphismConjuncts) {
+                        List<Expression> operandList = isomorphismConjunct.getExprList();
+                        VarIdentifier termVariable1 = ((VariableExpr) operandList.get(0)).getVar();
+                        VarIdentifier termVariable2 = ((VariableExpr) operandList.get(1)).getVar();
+                        if (visitedVariables.contains(termVariable1) && visitedVariables.contains(termVariable2)) {
+                            clauseIterator.add(new CorrWhereClause(isomorphismConjunct));
+                            appliedIsomorphismConjuncts.add(isomorphismConjunct);
+                        }
+                    }
+                    isomorphismConjuncts.removeAll(appliedIsomorphismConjuncts);
+                }
+            }
+        });
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java
new file mode 100644
index 0000000..bb9f849
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/action/PathPatternAction.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower.action;
+
+import java.util.ArrayList;
+import java.util.Collection;
+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.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;
+
+/**
+ * Build a path record out of a {@link PathPatternExpr} instance. This record contains two fields: "Vertices", which
+ * contains a list of all representative vertex variables, and "Edges", which contains a list of all representative
+ * 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) {
+        this.pathPatternExpr = pathPatternExpr;
+    }
+
+    @Override
+    public void apply(LoweringEnvironment loweringEnvironment) throws CompilationException {
+        if (pathPatternExpr.getVariableExpr() != null) {
+            loweringEnvironment.acceptTransformer(clauseSequence -> {
+                // We have a named non-sub-path.
+                RecordConstructor recordConstructor = new RecordConstructor();
+                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));
+            });
+        }
+        for (LetClause reboundSubPathExpression : pathPatternExpr.getReboundSubPathList()) {
+            loweringEnvironment.acceptTransformer(clauseSequence -> {
+                // 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));
+            });
+        }
+    }
+
+    public static void buildPathRecord(Collection<VertexPatternExpr> vertexExpressions,
+            List<EdgePatternExpr> edgeExpressions, RecordConstructor outputRecordConstructor) {
+        List<FieldBinding> fieldBindingList = new ArrayList<>();
+
+        // Assemble our vertices into a list.
+        List<Expression> vertexVariableExprList = vertexExpressions.stream().map(VertexPatternExpr::getVariableExpr)
+                .distinct().collect(Collectors.toList());
+        ListConstructor vertexVariableList = new ListConstructor();
+        vertexVariableList.setType(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR);
+        vertexVariableList.setExprList(vertexVariableExprList);
+        LiteralExpr verticesFieldName = new LiteralExpr(new StringLiteral(PATH_VERTICES_FIELD_NAME));
+        fieldBindingList.add(new FieldBinding(verticesFieldName, vertexVariableList));
+
+        // Assemble our edges into a list.
+        List<Expression> edgeVariableExprList =
+                edgeExpressions.stream().map(e -> e.getEdgeDescriptor().getVariableExpr()).collect(Collectors.toList());
+        ListConstructor edgeVariableList = new ListConstructor();
+        edgeVariableList.setType(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR);
+        edgeVariableList.setExprList(edgeVariableExprList);
+        LiteralExpr edgesFieldName = new LiteralExpr(new StringLiteral(PATH_EDGES_FIELD_NAME));
+        fieldBindingList.add(new FieldBinding(edgesFieldName, edgeVariableList));
+
+        // Set our field bindings in our output record constructor.
+        outputRecordConstructor.setFbList(fieldBindingList);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/AbstractLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/AbstractLowerAssembly.java
deleted file mode 100644
index e7459e0..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/AbstractLowerAssembly.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower.assembly;
-
-import java.util.ArrayList;
-import java.util.List;
-
-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.assembly.IExprAssembly;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierNode;
-import org.apache.asterix.graphix.lang.rewrites.util.EdgeRewritingUtil;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.WhereClause;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-
-/**
- * Abstract lowering assembly for creating a new {@link LowerSupplierNode} out of some input {@link LowerSupplierNode}.
- * There are two parts to function application here: (1) building a new FROM-TERM and (2) building a list of
- * projections- both of which will be used to create a FROM-CLAUSE that will be wrapped in a LET-CLAUSE.
- */
-public abstract class AbstractLowerAssembly implements IExprAssembly<LowerSupplierNode> {
-    protected final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
-    protected final LowerSupplierContext lowerSupplierContext;
-    protected final GraphIdentifier graphIdentifier;
-
-    public AbstractLowerAssembly(LowerSupplierContext lowerSupplierContext) {
-        this.elementLookupTable = lowerSupplierContext.getElementLookupTable();
-        this.graphIdentifier = lowerSupplierContext.getGraphIdentifier();
-        this.lowerSupplierContext = lowerSupplierContext;
-    }
-
-    protected abstract List<Projection> buildOutputProjections() throws CompilationException;
-
-    protected abstract FromTerm buildOutputFromTerm() throws CompilationException;
-
-    @Override
-    public LowerSupplierNode apply(LowerSupplierNode inputNode) throws CompilationException {
-        // Build the output FROM-TERM and PROJECTION list.
-        List<FromTerm> fromTermList = new ArrayList<>();
-        FromTerm outputFromTerm = buildOutputFromTerm();
-        List<Projection> projectionList = buildOutputProjections();
-        fromTermList.add(outputFromTerm);
-
-        Expression joinConjuncts = null;
-        if (inputNode != null) {
-            // Build a FROM-TERM using our input.
-            VariableExpr bindingInputLetVar = inputNode.getBindingVar();
-            VariableExpr leadingVariable = new VariableExpr(lowerSupplierContext.getNewVariable());
-            FromTerm inputFromTerm = new FromTerm(bindingInputLetVar, leadingVariable, null, null);
-            fromTermList.add(inputFromTerm);
-
-            // If we find any projections from our input that match our output, add the predicate here.
-            for (Projection outputProjection : projectionList) {
-                joinConjuncts = EdgeRewritingUtil.buildInputAssemblyJoin(inputNode, leadingVariable,
-                        outputProjection.getExpression(), joinConjuncts, n -> n.equals(outputProjection.getName()));
-            }
-
-            // Add all projections from our input, if they do not already exist.
-            for (Projection inputProjection : inputNode.getProjectionList()) {
-                VarIdentifier inputProjectionVariable = new VarIdentifier(inputProjection.getName());
-                VariableExpr inputProjectionVariableExpr = new VariableExpr(inputProjectionVariable);
-                if (projectionList.stream().map(Projection::getName)
-                        .noneMatch(p -> inputProjectionVariableExpr.getVar().getValue().equals(p))) {
-                    Projection outputProjection = new Projection(Projection.Kind.NAMED_EXPR,
-                            new FieldAccessor(leadingVariable, inputProjectionVariableExpr.getVar()),
-                            inputProjectionVariableExpr.getVar().getValue());
-                    projectionList.add(outputProjection);
-                }
-            }
-        }
-
-        // Finally, return the constructed lowering node.
-        List<AbstractClause> letWhereClauseList = new ArrayList<>();
-        if (joinConjuncts != null) {
-            letWhereClauseList.add(new WhereClause(joinConjuncts));
-        }
-        VariableExpr bindingOutputVar = new VariableExpr(lowerSupplierContext.getNewVariable());
-        return new LowerSupplierNode(fromTermList, letWhereClauseList, projectionList, bindingOutputVar);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/DanglingVertexLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/DanglingVertexLowerAssembly.java
deleted file mode 100644
index 7c93d2b..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/DanglingVertexLowerAssembly.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower.assembly;
-
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.record.VertexRecord;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.TrueLiteral;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-
-/**
- * Lower node for handling vertices that are not attached to an edge.
- * - The output FROM-TERM is a cross-product of all dangling vertices.
- * - The output projections are the variable expressions associated with each vertex.
- */
-public class DanglingVertexLowerAssembly extends AbstractLowerAssembly {
-    public DanglingVertexLowerAssembly(LowerSupplierContext lowerSupplierContext) {
-        super(lowerSupplierContext);
-    }
-
-    @Override
-    protected FromTerm buildOutputFromTerm() {
-        List<Expression> vertexExpressions = lowerSupplierContext.getDanglingVertices().stream()
-                .map(v -> new VertexRecord(v, graphIdentifier, lowerSupplierContext)).map(VertexRecord::getExpression)
-                .collect(Collectors.toList());
-
-        // Our FROM-TERM will start with the first defined vertex.
-        Expression leadingVertexExpression = vertexExpressions.get(0);
-        VariableExpr leadingVariable = lowerSupplierContext.getDanglingVertices().get(0).getVariableExpr();
-        FromTerm fromTerm = new FromTerm(leadingVertexExpression,
-                new VariableExpr(new VarIdentifier(leadingVariable.getVar())), null, null);
-
-        // Perform a CROSS-JOIN with all subsequent dangling vertices.
-        List<AbstractBinaryCorrelateClause> correlateClauses = fromTerm.getCorrelateClauses();
-        for (int i = 1; i < vertexExpressions.size(); i++) {
-            VertexPatternExpr followingVertexPattern = lowerSupplierContext.getDanglingVertices().get(i);
-            VariableExpr followingVariable = followingVertexPattern.getVariableExpr();
-            correlateClauses.add(new JoinClause(JoinType.INNER, vertexExpressions.get(i),
-                    new VariableExpr(new VarIdentifier(followingVariable.getVar())), null,
-                    new LiteralExpr(TrueLiteral.INSTANCE), null));
-        }
-
-        return fromTerm;
-    }
-
-    @Override
-    protected List<Projection> buildOutputProjections() {
-        return lowerSupplierContext.getDanglingVertices().stream().map(v -> {
-            String projectionName = SqlppVariableUtil.toUserDefinedName(v.getVariableExpr().getVar().getValue());
-            VariableExpr variableExpr = new VariableExpr(v.getVariableExpr().getVar());
-            return new Projection(Projection.Kind.NAMED_EXPR, variableExpr, projectionName);
-        }).collect(Collectors.toList());
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/ExpandEdgeLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/ExpandEdgeLowerAssembly.java
deleted file mode 100644
index cce1229..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/ExpandEdgeLowerAssembly.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower.assembly;
-
-import static org.apache.asterix.graphix.lang.rewrites.lower.assembly.IsomorphismLowerAssembly.getMatchEvaluationKind;
-import static org.apache.asterix.graphix.lang.rewrites.util.ClauseRewritingUtil.buildConnectedClauses;
-import static org.apache.asterix.graphix.lang.rewrites.util.EdgeRewritingUtil.buildEdgeKeyBuilder;
-import static org.apache.asterix.graphix.lang.rewrites.util.EdgeRewritingUtil.buildEdgeLabelPredicateBuilder;
-import static org.apache.asterix.graphix.lang.rewrites.util.EdgeRewritingUtil.buildVertexEdgeJoin;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSetOpInputWithFrom;
-
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
-import java.util.Iterator;
-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.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.assembly.ExprAssembler;
-import org.apache.asterix.graphix.lang.rewrites.expand.DirectedFixedPathExpansion;
-import org.apache.asterix.graphix.lang.rewrites.expand.DirectedVarPathExpansion;
-import org.apache.asterix.graphix.lang.rewrites.expand.IEdgePatternExpansion;
-import org.apache.asterix.graphix.lang.rewrites.expand.PathEnumerationEnvironment;
-import org.apache.asterix.graphix.lang.rewrites.expand.UndirectedEdgeExpansion;
-import org.apache.asterix.graphix.lang.rewrites.expand.UndirectedFixedPathExpansion;
-import org.apache.asterix.graphix.lang.rewrites.expand.UndirectedVarPathExpansion;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.record.EdgeRecord;
-import org.apache.asterix.graphix.lang.rewrites.record.VertexRecord;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.AbstractExpression;
-import org.apache.asterix.lang.common.base.Expression;
-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.VariableExpr;
-import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.optype.JoinType;
-import org.apache.asterix.lang.sqlpp.optype.SetOpType;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-
-/**
- * Edge expansion lowering assembly for handling {@link EdgePatternExpr} lowering.
- * - The output FROM-TERM is a collection of 2-way JOINs between the intermediate lowered edge ({@link EdgeRecord}
- * instance) and the corresponding vertices (as {@link VertexRecord} instances). The order of which vertex is JOINed
- * first depends on which vertex is the source and which vertex is the destination. In the case of a bidirectional edge
- * and/or sub-paths, we return a FROM-TERM that is the UNION of simple directed 1-hop edges.
- * - The output projections are minimally composed of the edge variable expression and the two corresponding vertex
- * variable expressions. If we have a bidirectional edge and/or a sub-path, then we must qualify all of our projections
- * to account for the additional subtree (i.e. the UNION-ALL).
- */
-public class ExpandEdgeLowerAssembly extends AbstractLowerAssembly {
-    public final static String METADATA_CONFIG_NAME = "expand-edge";
-
-    private final Deque<FromTermEnvironment> environmentStack;
-    private final EdgePatternExpr inputEdgePatternExpr;
-
-    // The lists below are specific to each FROM-TERM chain.
-    private final Deque<List<LetClause>> generatedReboundExpressions = new ArrayDeque<>();
-    private final Deque<List<Expression>> generatedEdgeConjuncts = new ArrayDeque<>();
-    private final Deque<List<Expression>> generatedVertexConjuncts = new ArrayDeque<>();
-
-    // We will introduce this after we collect each FROM-TERM chain.
-    private VariableExpr qualifyingIdentifier;
-
-    public ExpandEdgeLowerAssembly(EdgePatternExpr inputEdgePatternExpr, LowerSupplierContext lowerSupplierContext) {
-        super(lowerSupplierContext);
-        this.inputEdgePatternExpr = inputEdgePatternExpr;
-        this.environmentStack = new ArrayDeque<>();
-    }
-
-    private Iterable<Iterable<EdgePatternExpr>> expandEdgePatternExpr(EdgePatternExpr edgePatternExpr) {
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-
-        if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED
-                && edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
-            // We have a single directed edge. We do not need to break this up.
-            return List.of(List.of(edgePatternExpr));
-
-        } else if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED
-                && edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
-            return new UndirectedEdgeExpansion().expand(edgePatternExpr);
-
-        } else { // edgeDescriptor.getEdgeClass() == EdgeDescriptor.PatternType.PATH
-            PathEnumerationEnvironment decompositionEnvironment = new PathEnumerationEnvironment(edgePatternExpr,
-                    lowerSupplierContext, generatedVertexConjuncts::addLast, generatedEdgeConjuncts::addLast,
-                    generatedReboundExpressions::addLast);
-
-            IEdgePatternExpansion edgePatternDecomposition;
-            if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED
-                    && Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-                edgePatternDecomposition = new DirectedFixedPathExpansion(decompositionEnvironment);
-
-            } else if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED
-                    && Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-                edgePatternDecomposition = new UndirectedFixedPathExpansion(decompositionEnvironment);
-
-            } else if (edgeDescriptor.getEdgeDirection() != EdgeDescriptor.EdgeDirection.UNDIRECTED
-                    && !Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
-                edgePatternDecomposition = new DirectedVarPathExpansion(decompositionEnvironment);
-
-            } else { // == ...UNDIRECTED && !Objects.equals(...getMinimumHops(), ...getMaximumHops())
-                edgePatternDecomposition = new UndirectedVarPathExpansion(decompositionEnvironment);
-            }
-            return edgePatternDecomposition.expand(edgePatternExpr);
-        }
-    }
-
-    @Override
-    protected List<Projection> buildOutputProjections() {
-        VariableExpr leftVarExpr = inputEdgePatternExpr.getLeftVertex().getVariableExpr();
-        VariableExpr rightVarExpr = inputEdgePatternExpr.getRightVertex().getVariableExpr();
-        VariableExpr edgeVarExpr = inputEdgePatternExpr.getEdgeDescriptor().getVariableExpr();
-
-        // Qualify our output projections (if necessary).
-        AbstractExpression leftVar = leftVarExpr, rightVar = rightVarExpr, edgeVar = edgeVarExpr;
-        if (qualifyingIdentifier != null) {
-            VarIdentifier leftVarID = SqlppVariableUtil.toUserDefinedVariableName(leftVarExpr.getVar());
-            VarIdentifier rightVarID = SqlppVariableUtil.toUserDefinedVariableName(rightVarExpr.getVar());
-            VarIdentifier edgeVarID = SqlppVariableUtil.toUserDefinedVariableName(edgeVarExpr.getVar());
-            leftVar = new FieldAccessor(qualifyingIdentifier, leftVarID);
-            rightVar = new FieldAccessor(qualifyingIdentifier, rightVarID);
-            edgeVar = new FieldAccessor(qualifyingIdentifier, edgeVarID);
-        }
-
-        // Determine the name to assign to our projections.
-        String leftVarAs = SqlppVariableUtil.toUserDefinedName(leftVarExpr.getVar().getValue());
-        String rightVarAs = SqlppVariableUtil.toUserDefinedName(rightVarExpr.getVar().getValue());
-        String edgeVarAs = SqlppVariableUtil.toUserDefinedName(edgeVarExpr.getVar().getValue());
-
-        // Add our vertices and our edge.
-        List<Projection> projectionList = new ArrayList<>();
-        projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, leftVar, leftVarAs));
-        projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, rightVar, rightVarAs));
-        projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, edgeVar, edgeVarAs));
-
-        return projectionList;
-    }
-
-    @Override
-    protected FromTerm buildOutputFromTerm() throws CompilationException {
-        for (Iterable<EdgePatternExpr> edgePatternCollection : expandEdgePatternExpr(inputEdgePatternExpr)) {
-            // Build the initial FROM-TERM for this set of EDGE-PATTERN-EXPR.
-            ExprAssembler<FromTerm> fromTermAssembler = new ExprAssembler<>();
-            fromTermAssembler.bind(inputFromTerm -> {
-                VertexPatternExpr leftPattern = edgePatternCollection.iterator().next().getLeftVertex();
-                VertexRecord leftVertexRecord = new VertexRecord(leftPattern, graphIdentifier, lowerSupplierContext);
-                VarIdentifier leadingIdentifier = new VarIdentifier(leftVertexRecord.getVarExpr().getVar());
-                VariableExpr leadingVariable = new VariableExpr(leadingIdentifier);
-                return new FromTerm(leftVertexRecord.getExpression(), leadingVariable, null, null);
-            });
-
-            // Build our correlated clauses from this collection.
-            for (EdgePatternExpr edgePatternExpr : edgePatternCollection) {
-                EdgeRecord edgeRecord = new EdgeRecord(edgePatternExpr, graphIdentifier, lowerSupplierContext);
-
-                // Lower our left and right vertices.
-                VertexPatternExpr leftPattern = edgePatternExpr.getLeftVertex();
-                VertexPatternExpr rightPattern = edgePatternExpr.getRightVertex();
-                VertexRecord leftRecord = new VertexRecord(leftPattern, graphIdentifier, lowerSupplierContext);
-                VertexRecord rightRecord = new VertexRecord(rightPattern, graphIdentifier, lowerSupplierContext);
-
-                // Attach the correlated clauses associated with this edge.
-                EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-                fromTermAssembler.bind(inputFromTerm -> {
-                    // We need to determine which fields to access for our edge.
-                    Function<GraphElementIdentifier, ElementLabel> leftEdgeLabelAccess;
-                    Function<GraphElementIdentifier, ElementLabel> rightEdgeLabelAccess;
-                    Function<GraphElementIdentifier, List<List<String>>> leftEdgeKeyAccess;
-                    Function<GraphElementIdentifier, List<List<String>>> rightEdgeKeyAccess;
-                    if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
-                        leftEdgeKeyAccess = elementLookupTable::getEdgeSourceKeys;
-                        rightEdgeKeyAccess = elementLookupTable::getEdgeDestKeys;
-                        leftEdgeLabelAccess = elementLookupTable::getEdgeSourceLabel;
-                        rightEdgeLabelAccess = elementLookupTable::getEdgeDestLabel;
-
-                    } else {
-                        leftEdgeKeyAccess = elementLookupTable::getEdgeDestKeys;
-                        rightEdgeKeyAccess = elementLookupTable::getEdgeSourceKeys;
-                        leftEdgeLabelAccess = elementLookupTable::getEdgeDestLabel;
-                        rightEdgeLabelAccess = elementLookupTable::getEdgeSourceLabel;
-                    }
-
-                    // Build the join predicate to connect our left vertex to our edge...
-                    VariableExpr leftVar = new VariableExpr(leftRecord.getVarExpr().getVar());
-                    VariableExpr edgeVar = new VariableExpr(edgeRecord.getVarExpr().getVar());
-                    Expression leftToEdgeJoinPredicate =
-                            buildVertexEdgeJoin(leftRecord.getElementIdentifiers(), edgeRecord.getElementIdentifiers(),
-                                    buildEdgeLabelPredicateBuilder(leftVar, edgeVar, leftEdgeLabelAccess),
-                                    (v, e) -> v.getElementLabel().equals(leftEdgeLabelAccess.apply(e)),
-                                    v -> buildAccessorList(leftVar, elementLookupTable.getVertexKey(v)),
-                                    buildEdgeKeyBuilder(edgeVar, elementLookupTable, leftEdgeKeyAccess));
-
-                    // ...and attach the INNER-JOIN node to our FROM-TERM.
-                    VariableExpr leftToEdgeVar = new VariableExpr(new VarIdentifier(edgeVar.getVar()));
-                    inputFromTerm.getCorrelateClauses().add(new JoinClause(JoinType.INNER, edgeRecord.getExpression(),
-                            leftToEdgeVar, null, leftToEdgeJoinPredicate, null));
-
-                    // Build the join predicate to connect our right vertex to our edge...
-                    VariableExpr rightVar = new VariableExpr(rightRecord.getVarExpr().getVar());
-                    Expression rightToEdgeJoinPredicate =
-                            buildVertexEdgeJoin(rightRecord.getElementIdentifiers(), edgeRecord.getElementIdentifiers(),
-                                    buildEdgeLabelPredicateBuilder(rightVar, edgeVar, rightEdgeLabelAccess),
-                                    (v, e) -> v.getElementLabel().equals(rightEdgeLabelAccess.apply(e)),
-                                    v -> buildAccessorList(rightVar, elementLookupTable.getVertexKey(v)),
-                                    buildEdgeKeyBuilder(edgeVar, elementLookupTable, rightEdgeKeyAccess));
-
-                    // ...and attach the INNER-JOIN node to our FROM-TERM.
-                    VariableExpr edgeToRightVar = new VariableExpr(new VarIdentifier(rightVar.getVar()));
-                    inputFromTerm.getCorrelateClauses().add(new JoinClause(JoinType.INNER, rightRecord.getExpression(),
-                            edgeToRightVar, null, rightToEdgeJoinPredicate, null));
-
-                    return inputFromTerm;
-                });
-            }
-
-            // Save our state onto our stack.
-            FromTermEnvironment fromTermEnvironment = new FromTermEnvironment();
-            fromTermEnvironment.fromTermAssembler = fromTermAssembler;
-            fromTermEnvironment.outputProjections = buildOutputProjections();
-            fromTermEnvironment.outputLetWhereClauses = buildLetWhereClauses();
-            environmentStack.addLast(fromTermEnvironment);
-        }
-
-        // At this point, we should have a collection of FROM-TERMs. Build SET-OP-INPUT for each FROM-TERM.
-        Iterator<FromTermEnvironment> environmentIterator = environmentStack.iterator();
-        Deque<SetOperationInput> setOpInputStack =
-                environmentStack.stream().map(e -> e.fromTermAssembler.getLast()).map(f -> {
-                    FromTermEnvironment fromTermEnvironment = environmentIterator.next();
-                    List<Projection> outputProjections = fromTermEnvironment.outputProjections;
-                    List<AbstractClause> outputLetWhereClauses = fromTermEnvironment.outputLetWhereClauses;
-                    return buildSetOpInputWithFrom(f, outputProjections, outputLetWhereClauses);
-                }).collect(Collectors.toCollection(ArrayDeque::new));
-
-        // UNION-ALL each SET-OP-INPUT (if we have any).
-        SetOperationInput leftSetOpInput = setOpInputStack.removeFirst();
-        SelectSetOperation selectSetOperation = new SelectSetOperation(leftSetOpInput, setOpInputStack.stream()
-                .map(s -> new SetOperationRight(SetOpType.UNION, false, s)).collect(Collectors.toList()));
-
-        // We return a new FROM-TERM which will bind the SELECT-EXPR above to our qualifying var.
-        qualifyingIdentifier = new VariableExpr(lowerSupplierContext.getNewVariable());
-        SelectExpression selectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
-        return new FromTerm(selectExpression, qualifyingIdentifier, null, null);
-    }
-
-    private List<AbstractClause> buildLetWhereClauses() throws CompilationException {
-        // Determine if any isomorphism is desired.
-        List<Expression> isomorphismConjuncts = new ArrayList<>();
-        switch (getMatchEvaluationKind(lowerSupplierContext.getMetadataProvider())) {
-            case TOTAL_ISOMORPHISM:
-            case VERTEX_ISOMORPHISM:
-                if (!generatedVertexConjuncts.isEmpty()) {
-                    isomorphismConjuncts = generatedVertexConjuncts.removeFirst();
-                }
-                break;
-
-            case EDGE_ISOMORPHISM:
-                if (!generatedEdgeConjuncts.isEmpty()) {
-                    isomorphismConjuncts = generatedEdgeConjuncts.removeFirst();
-                }
-                break;
-        }
-
-        // Finally, build our LET-WHERE list.
-        List<AbstractClause> letWhereClauseList = new ArrayList<>();
-        if (!isomorphismConjuncts.isEmpty()) {
-            letWhereClauseList.add(new WhereClause(buildConnectedClauses(isomorphismConjuncts, OperatorType.AND)));
-        }
-        if (!generatedReboundExpressions.isEmpty() && !generatedReboundExpressions.getLast().isEmpty()) {
-            letWhereClauseList.addAll(generatedReboundExpressions.removeFirst());
-        }
-        return letWhereClauseList;
-    }
-
-    // If multiple FROM-TERMs are required, our environment stack will have more than one element.
-    private static class FromTermEnvironment {
-        ExprAssembler<FromTerm> fromTermAssembler;
-        List<Projection> outputProjections;
-        List<AbstractClause> outputLetWhereClauses;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/IsomorphismLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/IsomorphismLowerAssembly.java
deleted file mode 100644
index b8e7242..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/IsomorphismLowerAssembly.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower.assembly;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.ClauseRewritingUtil.buildConnectedClauses;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-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.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.assembly.IExprAssembly;
-import org.apache.asterix.graphix.lang.rewrites.common.EdgeDependencyGraph;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierNode;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementResolutionVisitor;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.WhereClause;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Define which graph elements are *not* equal to each other. We assume that all elements are named at this point
- * (i.e. {@link ElementResolutionVisitor} must run before this). We enforce the following:
- * 1. By default, we enforce vertex isomorphism. No vertex (and consequently, no edge) can appear more than once across
- * all patterns of all {@link MatchClause} nodes.
- * 2. If edge-isomorphism is desired, then we only enforce that no edge can appear more than once across all
- * {@link MatchClause} nodes.
- * 3. If homomorphism is desired, then we enforce nothing. Edge adjacency is already implicitly preserved.
- * 4. If we have any LEFT-MATCH clauses, then we need to ensure that vertices that are only found in LEFT-MATCH clauses
- * are allowed to have a MISSING value.
- */
-public class IsomorphismLowerAssembly implements IExprAssembly<LowerSupplierNode> {
-    public enum MatchEvaluationKind {
-        TOTAL_ISOMORPHISM("isomorphism"), // = VERTEX_ISOMORPHISM
-        VERTEX_ISOMORPHISM("vertex-isomorphism"),
-        EDGE_ISOMORPHISM("edge-isomorphism"),
-        HOMOMORPHISM("homomorphism");
-
-        // The user specifies the options above through "SET `graphix.match-evaluation` '...';"
-        private final String metadataConfigOptionName;
-
-        MatchEvaluationKind(String metadataConfigOptionName) {
-            this.metadataConfigOptionName = metadataConfigOptionName;
-        }
-
-        @Override
-        public String toString() {
-            return metadataConfigOptionName;
-        }
-    }
-
-    private final List<VertexPatternExpr> danglingVertices;
-    private final List<VarIdentifier> optionalVertices;
-    private final EdgeDependencyGraph edgeDependencyGraph;
-    private final MetadataProvider metadataProvider;
-
-    public IsomorphismLowerAssembly(List<VertexPatternExpr> danglingVertices, List<VarIdentifier> optionalVertices,
-            EdgeDependencyGraph edgeDependencyGraph, MetadataProvider metadataProvider) {
-        this.danglingVertices = danglingVertices;
-        this.optionalVertices = optionalVertices;
-        this.edgeDependencyGraph = edgeDependencyGraph;
-        this.metadataProvider = metadataProvider;
-    }
-
-    public static MatchEvaluationKind getMatchEvaluationKind(MetadataProvider metadataProvider)
-            throws CompilationException {
-        final String metadataConfigKeyName = GraphixCompilationProvider.MATCH_EVALUATION_METADATA_CONFIG;
-
-        if (metadataProvider.getConfig().containsKey(metadataConfigKeyName)) {
-            String metadataConfigKeyValue = (String) metadataProvider.getConfig().get(metadataConfigKeyName);
-            if (metadataConfigKeyValue.equalsIgnoreCase(MatchEvaluationKind.TOTAL_ISOMORPHISM.toString())) {
-                return MatchEvaluationKind.TOTAL_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(MatchEvaluationKind.VERTEX_ISOMORPHISM.toString())) {
-                return MatchEvaluationKind.VERTEX_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(MatchEvaluationKind.EDGE_ISOMORPHISM.toString())) {
-                return MatchEvaluationKind.EDGE_ISOMORPHISM;
-
-            } else if (metadataConfigKeyValue.equalsIgnoreCase(MatchEvaluationKind.HOMOMORPHISM.toString())) {
-                return MatchEvaluationKind.HOMOMORPHISM;
-
-            } else {
-                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, metadataConfigKeyValue);
-            }
-
-        } else {
-            return MatchEvaluationKind.VERTEX_ISOMORPHISM;
-        }
-    }
-
-    public static List<Expression> generateIsomorphismConjuncts(List<Expression> variableList) {
-        List<Expression> 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;
-    }
-
-    public static void populateVariableMap(Set<ElementLabel> labels, VariableExpr variableExpr,
-            Map<ElementLabel, List<VariableExpr>> labelVariableMap) {
-        Function<VariableExpr, Boolean> mapMatchFinder = v -> {
-            final String variableName = SqlppVariableUtil.toUserDefinedName(variableExpr.getVar().getValue());
-            String inputVariableName = SqlppVariableUtil.toUserDefinedName(v.getVar().getValue());
-            return variableName.equals(inputVariableName);
-        };
-        for (ElementLabel label : labels) {
-            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);
-            }
-        }
-        if (labels.isEmpty()) {
-            if (labelVariableMap.containsKey(null)) {
-                if (labelVariableMap.get(null).stream().noneMatch(mapMatchFinder::apply)) {
-                    labelVariableMap.get(null).add(variableExpr);
-                }
-
-            } else {
-                List<VariableExpr> variableList = new ArrayList<>();
-                variableList.add(variableExpr);
-                labelVariableMap.put(null, variableList);
-            }
-        }
-    }
-
-    @Override
-    public LowerSupplierNode apply(LowerSupplierNode inputLowerNode) throws CompilationException {
-        Map<ElementLabel, List<VariableExpr>> vertexVariableMap = new HashMap<>();
-        Map<ElementLabel, List<VariableExpr>> edgeVariableMap = new HashMap<>();
-        Map<VariableExpr, Expression> qualifiedExprMap = new HashMap<>();
-
-        // Fetch the edge and vertex variables, qualified through our input projections.
-        Map<String, Expression> qualifiedProjectionMap = inputLowerNode.getProjectionList().stream()
-                .collect(Collectors.toMap(Projection::getName, Projection::getExpression));
-        for (VertexPatternExpr danglingVertex : danglingVertices) {
-            VariableExpr vertexVarExpr = danglingVertex.getVariableExpr();
-            String vertexVarName = SqlppVariableUtil.toUserDefinedName(vertexVarExpr.getVar().getValue());
-            populateVariableMap(danglingVertex.getLabels(), vertexVarExpr, vertexVariableMap);
-            qualifiedExprMap.put(vertexVarExpr, qualifiedProjectionMap.get(vertexVarName));
-        }
-        edgeDependencyGraph.forEach(p -> p.forEach(e -> {
-            // Handle our left and right vertices.
-            VariableExpr leftVertexVar = e.getLeftVertex().getVariableExpr();
-            VariableExpr rightVertexVar = e.getRightVertex().getVariableExpr();
-            populateVariableMap(e.getLeftVertex().getLabels(), leftVertexVar, vertexVariableMap);
-            populateVariableMap(e.getRightVertex().getLabels(), rightVertexVar, vertexVariableMap);
-            String leftVarName = SqlppVariableUtil.toUserDefinedName(leftVertexVar.getVar().getValue());
-            String rightVarName = SqlppVariableUtil.toUserDefinedName(rightVertexVar.getVar().getValue());
-            qualifiedExprMap.put(leftVertexVar, qualifiedProjectionMap.get(leftVarName));
-            qualifiedExprMap.put(rightVertexVar, qualifiedProjectionMap.get(rightVarName));
-
-            // If we have an edge (and not a sub-path), create a conjunct for this.
-            if (e.getEdgeDescriptor().getPatternType() == EdgeDescriptor.PatternType.EDGE) {
-                VariableExpr edgeVar = e.getEdgeDescriptor().getVariableExpr();
-                populateVariableMap(e.getEdgeDescriptor().getEdgeLabels(), edgeVar, edgeVariableMap);
-                String edgeVarName = SqlppVariableUtil.toUserDefinedName(edgeVar.getVar().getValue());
-                qualifiedExprMap.put(edgeVar, qualifiedProjectionMap.get(edgeVarName));
-            }
-        }));
-
-        // Construct our isomorphism conjuncts.
-        List<Expression> isomorphismConjuncts = new ArrayList<>();
-        switch (getMatchEvaluationKind(metadataProvider)) {
-            case TOTAL_ISOMORPHISM:
-            case VERTEX_ISOMORPHISM:
-                vertexVariableMap.values().stream()
-                        .map(v -> v.stream().map(qualifiedExprMap::get).collect(Collectors.toList()))
-                        .map(IsomorphismLowerAssembly::generateIsomorphismConjuncts)
-                        .forEach(isomorphismConjuncts::addAll);
-                break;
-
-            case EDGE_ISOMORPHISM:
-                edgeVariableMap.values().stream()
-                        .map(e -> e.stream().map(qualifiedExprMap::get).collect(Collectors.toList()))
-                        .map(IsomorphismLowerAssembly::generateIsomorphismConjuncts)
-                        .forEach(isomorphismConjuncts::addAll);
-                break;
-        }
-
-        // Construct our IF_MISSING disjuncts.
-        List<Expression> ifMissingExprList = null;
-        if (!optionalVertices.isEmpty()) {
-            ifMissingExprList = optionalVertices.stream().map(v -> {
-                String qualifiedVarName = SqlppVariableUtil.toUserDefinedVariableName(v).getValue();
-                Expression qualifiedVar = qualifiedProjectionMap.get(qualifiedVarName);
-                return new CallExpr(new FunctionSignature(BuiltinFunctions.IS_MISSING), List.of(qualifiedVar));
-            }).collect(Collectors.toList());
-        }
-
-        // Attach our WHERE-EXPR to the tail node.
-        if (!isomorphismConjuncts.isEmpty() && ifMissingExprList == null) {
-            Expression whereExpr = buildConnectedClauses(isomorphismConjuncts, OperatorType.AND);
-            inputLowerNode.getWhereClauses().add(new WhereClause(whereExpr));
-
-        } else if (isomorphismConjuncts.isEmpty() && ifMissingExprList != null) {
-            Expression whereExpr = buildConnectedClauses(ifMissingExprList, OperatorType.OR);
-            inputLowerNode.getWhereClauses().add(new WhereClause(whereExpr));
-
-        } else if (!isomorphismConjuncts.isEmpty()) { // && ifMissingExprList != null
-            Expression isomorphismExpr = buildConnectedClauses(isomorphismConjuncts, OperatorType.AND);
-            Expression ifMissingExpr = buildConnectedClauses(ifMissingExprList, OperatorType.OR);
-            List<Expression> whereExprDisjuncts = List.of(isomorphismExpr, ifMissingExpr);
-            Expression whereExpr = buildConnectedClauses(whereExprDisjuncts, OperatorType.OR);
-            inputLowerNode.getWhereClauses().add(new WhereClause(whereExpr));
-        }
-        return inputLowerNode;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/NamedPathLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/NamedPathLowerAssembly.java
deleted file mode 100644
index 671a941..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/NamedPathLowerAssembly.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.lower.assembly;
-
-import static org.apache.asterix.lang.common.expression.ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.assembly.IExprAssembly;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierNode;
-import org.apache.asterix.graphix.lang.rewrites.record.PathRecord;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.expression.ListConstructor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.asterix.om.functions.BuiltinFunctions;
-
-/**
- * Introduce named paths into the given assembly node. If a sub-path is found within a {@link PathPatternExpr}, we need
- * to merge the sub-path into the full-path. If the pattern only consists of a vertex, then a pseudo-path is formed with
- * special "dangling-vertex" edge.
- */
-public class NamedPathLowerAssembly implements IExprAssembly<LowerSupplierNode> {
-    private final List<PathPatternExpr> pathPatternExprList;
-
-    public NamedPathLowerAssembly(List<PathPatternExpr> pathPatternExprList) {
-        this.pathPatternExprList = pathPatternExprList;
-    }
-
-    @Override
-    public LowerSupplierNode apply(LowerSupplierNode inputLowerNode) throws CompilationException {
-        Map<String, Expression> qualifiedProjectionMap = inputLowerNode.getProjectionList().stream()
-                .collect(Collectors.toMap(Projection::getName, Projection::getExpression));
-        for (PathPatternExpr pathPatternExpr : pathPatternExprList) {
-            if (pathPatternExpr.getVariableExpr() == null) {
-                continue;
-            }
-            if (!pathPatternExpr.getEdgeExpressions().isEmpty()) {
-                List<Expression> pathExpressionParts = new ArrayList<>();
-                List<Expression> workingSimplePath = new ArrayList<>();
-
-                for (EdgePatternExpr edgePatternExpr : pathPatternExpr.getEdgeExpressions()) {
-                    EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-                    if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.EDGE) {
-                        // "Raw" edges are first put into a list of path records.
-                        VariableExpr leftVertexVar = edgePatternExpr.getLeftVertex().getVariableExpr();
-                        VariableExpr rightVertexVar = edgePatternExpr.getRightVertex().getVariableExpr();
-                        VariableExpr edgeVar = edgeDescriptor.getVariableExpr();
-
-                        // Get the name of our path variables.
-                        String edgeVarName = SqlppVariableUtil.toUserDefinedName(edgeVar.getVar().getValue());
-                        String leftVarName = SqlppVariableUtil.toUserDefinedName(leftVertexVar.getVar().getValue());
-                        String rightVarName = SqlppVariableUtil.toUserDefinedName(rightVertexVar.getVar().getValue());
-
-                        // We must qualify each of our expressions.
-                        Expression leftExpr = qualifiedProjectionMap.get(leftVarName);
-                        Expression rightExpr = qualifiedProjectionMap.get(rightVarName);
-                        Expression edgeExpr = qualifiedProjectionMap.get(edgeVarName);
-                        workingSimplePath.add(new PathRecord(leftExpr, edgeExpr, rightExpr).getExpression());
-
-                    } else { // edgeDescriptor.getEdgeClass() == EdgeDescriptor.PatternType.PATH
-                        // If we encounter a sub-path, ARRAY_CONCAT our existing path and sub-path.
-                        pathExpressionParts.add(new ListConstructor(ORDERED_LIST_CONSTRUCTOR, workingSimplePath));
-                        VariableExpr subPathVar = edgeDescriptor.getVariableExpr();
-                        String subPathVarName = SqlppVariableUtil.toUserDefinedName(subPathVar.getVar().getValue());
-                        pathExpressionParts.add(qualifiedProjectionMap.get(subPathVarName));
-                        workingSimplePath = new ArrayList<>();
-                    }
-                }
-
-                // If did not encounter a sub-path, build our simple path here.
-                if (!workingSimplePath.isEmpty()) {
-                    pathExpressionParts.add(new ListConstructor(ORDERED_LIST_CONSTRUCTOR, workingSimplePath));
-                }
-
-                String pathVariableName = pathPatternExpr.getVariableExpr().getVar().getValue();
-                if (pathExpressionParts.size() == 1) {
-                    // If we only have one expression, we do not need to ARRAY_CONCAT.
-                    inputLowerNode.getProjectionList().add(new Projection(Projection.Kind.NAMED_EXPR,
-                            pathExpressionParts.get(0), SqlppVariableUtil.toUserDefinedName(pathVariableName)));
-
-                } else {
-                    // ...otherwise, ARRAY_CONCAT (we do this to preserve the order of our path records).
-                    FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.ARRAY_CONCAT);
-                    Projection projection = new Projection(Projection.Kind.NAMED_EXPR,
-                            new CallExpr(functionSignature, pathExpressionParts),
-                            SqlppVariableUtil.toUserDefinedName(pathVariableName));
-                    inputLowerNode.getProjectionList().add(projection);
-                }
-
-            } else {
-                // We only have one vertex (no edges). Attach a singleton list of a single-field record.
-                VariableExpr danglingVertexVar = pathPatternExpr.getVertexExpressions().get(0).getVariableExpr();
-                String danglingVarName = SqlppVariableUtil.toUserDefinedName(danglingVertexVar.getVar().getValue());
-                Expression danglingExpr = qualifiedProjectionMap.get(danglingVarName);
-                List<Expression> pathExpressions = List.of(new PathRecord(danglingExpr).getExpression());
-                inputLowerNode.getProjectionList().add(new Projection(Projection.Kind.NAMED_EXPR,
-                        new ListConstructor(ORDERED_LIST_CONSTRUCTOR, pathExpressions),
-                        SqlppVariableUtil.toUserDefinedName(pathPatternExpr.getVariableExpr().getVar().getValue())));
-            }
-        }
-        return inputLowerNode;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java
new file mode 100644
index 0000000..3ea61d1
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/CorrelatedClauseSequence.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.lower.transform;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+
+/**
+ * A "general" list for maintaining a sequence of {@link AbstractBinaryCorrelateClause} AST nodes. We have:
+ * 1. A list of main clauses, which our callers have free rein to manipulate.
+ * 2. A list of deferred clauses, which will be inserted at the tail end of the final list.
+ * 3. A list of vertex bindings, which will be inserted immediately after the main clause list.
+ * 4. A list of edge bindings, which will be inserted immediately after the vertex binding list.
+ */
+public class CorrelatedClauseSequence implements Iterable<AbstractBinaryCorrelateClause> {
+    private final LinkedList<AbstractBinaryCorrelateClause> deferredCorrelatedClauseSequence = new LinkedList<>();
+    private final LinkedList<AbstractBinaryCorrelateClause> mainCorrelatedClauseSequence = new LinkedList<>();
+    private final List<CorrLetClause> representativeVertexBindings = new ArrayList<>();
+    private final List<CorrLetClause> representativeEdgeBindings = new ArrayList<>();
+
+    public void addMainClause(AbstractBinaryCorrelateClause correlateClause) {
+        mainCorrelatedClauseSequence.addLast(correlateClause);
+    }
+
+    public void addDeferredClause(AbstractBinaryCorrelateClause correlateClause) {
+        deferredCorrelatedClauseSequence.add(correlateClause);
+    }
+
+    public void addRepresentativeVertexBinding(VarIdentifier representativeVariable, Expression bindingExpression) {
+        VariableExpr representativeVariableExpr = new VariableExpr(representativeVariable);
+        representativeVertexBindings.add(new CorrLetClause(bindingExpression, representativeVariableExpr, null));
+    }
+
+    public void addRepresentativeEdgeBinding(VarIdentifier representativeVariable, Expression bindingExpression) {
+        VariableExpr representativeVariableExpr = new VariableExpr(representativeVariable);
+        representativeEdgeBindings.add(new CorrLetClause(bindingExpression, representativeVariableExpr, null));
+    }
+
+    public AbstractBinaryCorrelateClause popMainClauseSequence() {
+        return mainCorrelatedClauseSequence.removeFirst();
+    }
+
+    public ListIterator<AbstractBinaryCorrelateClause> getMainIterator() {
+        return mainCorrelatedClauseSequence.listIterator();
+    }
+
+    public List<CorrLetClause> getRepresentativeVertexBindings() {
+        return representativeVertexBindings;
+    }
+
+    public List<CorrLetClause> getRepresentativeEdgeBindings() {
+        return representativeEdgeBindings;
+    }
+
+    @Override
+    public Iterator<AbstractBinaryCorrelateClause> iterator() {
+        List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<>();
+        correlateClauses.addAll(mainCorrelatedClauseSequence);
+        correlateClauses.addAll(representativeVertexBindings);
+        correlateClauses.addAll(representativeEdgeBindings);
+        correlateClauses.addAll(deferredCorrelatedClauseSequence);
+        return correlateClauses.listIterator();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
similarity index 81%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
index 25efe98..4509792 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/transform/ISequenceTransformer.java
@@ -16,14 +16,11 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+package org.apache.asterix.graphix.lang.rewrites.lower.transform;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 
-/**
- * @see ExprAssembler
- */
 @FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public interface ISequenceTransformer {
+    void accept(CorrelatedClauseSequence clauseSequence) throws CompilationException;
 }
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/rewrites/print/GraphixASTPrintVisitor.java
index 867ba00..fba1762 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/rewrites/print/GraphixASTPrintVisitor.java
@@ -32,8 +32,9 @@
 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.GraphElementDecl;
+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.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
@@ -253,12 +254,17 @@
 
     // The following should not appear in queries (the former, pre-rewrite).
     @Override
+    public Void visit(DeclareGraphStatement declareGraphStatement, Integer arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
     public Void visit(CreateGraphStatement createGraphStatement, Integer step) throws CompilationException {
         return null;
     }
 
     @Override
-    public Void visit(GraphElementDecl graphElementDecl, Integer step) throws CompilationException {
+    public Void visit(GraphElementDeclaration graphElementDeclaration, Integer step) throws CompilationException {
         return null;
     }
 
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/rewrites/print/SqlppASTPrintQueryVisitor.java
index 4493937..0a1b942 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/rewrites/print/SqlppASTPrintQueryVisitor.java
@@ -23,6 +23,8 @@
 import java.util.stream.IntStream;
 
 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.lang.common.base.Clause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
@@ -46,6 +48,7 @@
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.common.struct.OperatorType;
+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.HavingClause;
@@ -69,12 +72,13 @@
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
- * Visitor class to create a valid SQL++ query out of a **valid** AST. We make the following assumptions:
+ * 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 positional variables are included.
- * 4. No {@link WindowExpression} nodes are included (this is on the TODO list).
+ * 3. No {@link WindowExpression} nodes are included (this is on the TODO list).
  */
 public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisitor<String, Void> {
     private final PrintWriter printWriter;
@@ -85,6 +89,7 @@
 
     @Override
     public String visit(Query query, Void arg) throws CompilationException {
+        // TODO (GLENN): We can avoid this regex altogether if we cleanup the spaces in the other dispatch methods.
         String queryBodyString = query.getBody().accept(this, arg) + ";";
         queryBodyString = queryBodyString.trim().replaceAll("\\s+", " ");
         printWriter.print(queryBodyString);
@@ -195,15 +200,22 @@
 
     @Override
     public String visit(FromTerm fromTerm, Void arg) throws CompilationException {
-        // Note: we do not include positional variables here.
         StringBuilder sb = new StringBuilder();
         sb.append(fromTerm.getLeftExpression().accept(this, arg));
         if (fromTerm.getLeftVariable() != null) {
             sb.append(" AS ");
             sb.append(fromTerm.getLeftVariable().accept(this, arg));
         }
-        sb.append(fromTerm.getCorrelateClauses().stream().map(this::visitAndSwallowException)
-                .collect(Collectors.joining()));
+        if (fromTerm.hasPositionalVariable()) {
+            sb.append(" AT ");
+            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();
     }
 
@@ -216,15 +228,11 @@
     public String visit(GroupbyClause groupbyClause, Void arg) throws CompilationException {
         StringBuilder sb = new StringBuilder();
         sb.append(" GROUP BY "); // Note: we should have rewritten grouping sets by now.
-        sb.append(groupbyClause.getGbyPairList().stream().flatMap(Collection::stream).map(p -> {
-            if (p.getVar() != null) {
-                return " ( " + visitAndSwallowException(p.getExpr()) + " AS " + visitAndSwallowException(p.getVar())
-                        + ")";
-
-            } else {
-                return " ( " + visitAndSwallowException(p.getExpr()) + " ) ";
-            }
-        }).collect(Collectors.joining(", ")));
+        sb.append(groupbyClause.getGbyPairList().stream().flatMap(Collection::stream)
+                .map(p -> (p.getVar() != null) ? (" ( " + visitAndSwallowException(p.getExpr()) + " AS "
+                        + visitAndSwallowException(p.getVar()) + ")")
+                        : (" ( " + visitAndSwallowException(p.getExpr()) + " ) "))
+                .collect(Collectors.joining(", ")));
         if (groupbyClause.hasGroupVar()) {
             sb.append(" GROUP AS ");
             sb.append(groupbyClause.getGroupVar().accept(this, arg));
@@ -291,7 +299,6 @@
 
     @Override
     public String visit(JoinClause joinClause, Void arg) throws CompilationException {
-        // Note: we do not include positional variables here.
         StringBuilder sb = new StringBuilder();
         switch (joinClause.getJoinType()) {
             case INNER:
@@ -309,6 +316,10 @@
             sb.append(" AS ");
             sb.append(joinClause.getRightVariable().accept(this, arg));
         }
+        if (joinClause.hasPositionalVariable()) {
+            sb.append(" AT ");
+            sb.append(joinClause.getPositionalVariable().accept(this, arg));
+        }
         sb.append(" ON ");
         sb.append(joinClause.getConditionExpression().accept(this, arg));
         return sb.toString();
@@ -321,7 +332,6 @@
 
     @Override
     public String visit(UnnestClause unnestClause, Void arg) throws CompilationException {
-        // Note: we do not include positional variables here.
         StringBuilder sb = new StringBuilder();
         switch (unnestClause.getUnnestType()) {
             case INNER:
@@ -336,6 +346,10 @@
             sb.append(" AS ");
             sb.append(unnestClause.getRightVariable().accept(this, arg));
         }
+        if (unnestClause.hasPositionalVariable()) {
+            sb.append(" AT ");
+            sb.append(unnestClause.getPositionalVariable().accept(this, arg));
+        }
         return sb.toString();
     }
 
@@ -587,6 +601,7 @@
             switch (unaryExpr.getExprType()) {
                 case POSITIVE:
                     sb.append("+");
+                    break;
                 case NEGATIVE:
                     sb.append("-");
                     break;
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/EdgeRecord.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/EdgeRecord.java
deleted file mode 100644
index 66d9e76..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/EdgeRecord.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.record;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSelectBlocksWithDecls;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSelectWithSelectBlockStream;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-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.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-
-/**
- * Intermediate lowered representation of a query edge (just the body), constructed with the following process:
- * 1. For each label of the edge, perform a UNION-ALL of their respective normalized bodies.
- * 2. Build a SELECT-BLOCK for each edge label, using the result of step 1. For each SELECT-CLAUSE, attach the edge
- * label, the edge direction, access to the source key, and access to the destination key, in addition to the
- * expression from step 1.
- * 3. Now having a list of SELECT-BLOCKs, UNION-ALL each SELECT-BLOCK and build a single SELECT-EXPR. This is the
- * lowered representation of our edge.
- * If this is a sub-path, we will also keep track of the contained {@link VertexRecord} here.
- */
-public class EdgeRecord implements IElementRecord {
-    // The details surrounding our edge will be included in the following field (alongside our body).
-    public static final String EDGE_DETAIL_NAME = "_GraphixEdgeDetail";
-
-    // We attach the following fields to the SELECT containing a declaration body.
-    public static final String DIRECTION_FIELD_NAME = "EdgeDirection";
-    public static final String SOURCE_KEY_FIELD_NAME = "SourceKey";
-    public static final String DEST_KEY_FIELD_NAME = "DestinationKey";
-
-    private final List<GraphElementIdentifier> elementIdentifiers;
-    private final List<VertexRecord> internalVertices;
-    private final EdgePatternExpr edgePatternExpr;
-    private final Expression edgeExpression;
-    private final VariableExpr edgeVar;
-
-    public EdgeRecord(EdgePatternExpr edgePatternExpr, GraphIdentifier graphIdentifier,
-            LowerSupplierContext lowerSupplierContext) {
-        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-        this.elementIdentifiers = edgeDescriptor.generateIdentifiers(graphIdentifier);
-        this.edgeVar = new VariableExpr(edgeDescriptor.getVariableExpr().getVar());
-        this.edgePatternExpr = edgePatternExpr;
-
-        // Build the SELECT-EXPR associated with the given element identifiers.
-        ElementLookupTable<GraphElementIdentifier> elementLookupTable = lowerSupplierContext.getElementLookupTable();
-        List<GraphElementDecl> graphElementDeclList =
-                elementIdentifiers.stream().map(elementLookupTable::getElementDecl).collect(Collectors.toList());
-        List<SelectBlock> selectBlockList = buildSelectBlocksWithDecls(graphElementDeclList, edgeVar, d -> {
-            List<Projection> projectionList = new ArrayList<>();
-
-            // Build the label binding for the element detail projection.
-            List<FieldBinding> elementDetailBindings = new ArrayList<>();
-            StringLiteral labelLiteral = new StringLiteral(d.getIdentifier().getElementLabel().toString());
-            LiteralExpr labelFieldValue = new LiteralExpr(labelLiteral);
-            LiteralExpr labelFieldName = new LiteralExpr(new StringLiteral(ELEMENT_LABEL_FIELD_NAME));
-            elementDetailBindings.add(new FieldBinding(labelFieldName, labelFieldValue));
-            RecordConstructor elementDetailRecord = new RecordConstructor(elementDetailBindings);
-            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, elementDetailRecord, ELEMENT_DETAIL_NAME));
-
-            // Build the direction binding for the edge detail projection.
-            List<FieldBinding> edgeDetailBindings = new ArrayList<>();
-            LiteralExpr directionFieldValue =
-                    new LiteralExpr(new StringLiteral(edgeDescriptor.getEdgeDirection().toString().toUpperCase()));
-            LiteralExpr directionFieldName = new LiteralExpr(new StringLiteral(DIRECTION_FIELD_NAME));
-            edgeDetailBindings.add(new FieldBinding(directionFieldName, directionFieldValue));
-
-            // Access our source key value, and append this expression.
-            List<FieldBinding> sourceKeyBindings =
-                    buildAccessorList(edgeVar, elementLookupTable.getEdgeSourceKeys(d.getIdentifier())).stream()
-                            .map(LowerRewritingUtil::buildFieldBindingFromFieldAccessor).collect(Collectors.toList());
-            RecordConstructor sourceKeyAccess = new RecordConstructor(sourceKeyBindings);
-            LiteralExpr sourceKeyName = new LiteralExpr(new StringLiteral(SOURCE_KEY_FIELD_NAME));
-            edgeDetailBindings.add(new FieldBinding(sourceKeyName, sourceKeyAccess));
-
-            // Access our destination key value, and append this expression.
-            List<FieldBinding> destKeyBindings =
-                    buildAccessorList(edgeVar, elementLookupTable.getEdgeDestKeys(d.getIdentifier())).stream()
-                            .map(LowerRewritingUtil::buildFieldBindingFromFieldAccessor).collect(Collectors.toList());
-            RecordConstructor destKeyAccess = new RecordConstructor(destKeyBindings);
-            LiteralExpr destKeyName = new LiteralExpr(new StringLiteral(DEST_KEY_FIELD_NAME));
-            edgeDetailBindings.add(new FieldBinding(destKeyName, destKeyAccess));
-            RecordConstructor edgeDetailRecord = new RecordConstructor(edgeDetailBindings);
-            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, edgeDetailRecord, EDGE_DETAIL_NAME));
-
-            // Return our new assembled projections.
-            return projectionList;
-        });
-        this.edgeExpression = buildSelectWithSelectBlockStream(selectBlockList.stream());
-
-        // Build the VERTEX-RECORD(s) associated with the sub-path.
-        this.internalVertices = new ArrayList<>();
-        edgePatternExpr.getInternalVertices().stream()
-                .map(v -> new VertexRecord(v, graphIdentifier, lowerSupplierContext)).forEach(internalVertices::add);
-    }
-
-    @Override
-    public Expression getExpression() {
-        return edgeExpression;
-    }
-
-    public List<GraphElementIdentifier> getElementIdentifiers() {
-        return elementIdentifiers;
-    }
-
-    public VariableExpr getVarExpr() {
-        return edgeVar;
-    }
-
-    public EdgePatternExpr getEdgePatternExpr() {
-        return edgePatternExpr;
-    }
-
-    public List<VertexRecord> getInternalVertices() {
-        return internalVertices;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java
deleted file mode 100644
index 2a4d8c5..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.record;
-
-import org.apache.asterix.lang.common.base.Expression;
-
-public interface IElementRecord {
-    // The details surrounding our element will be included in the following field (alongside our body).
-    String ELEMENT_DETAIL_NAME = "_GraphixElementDetail";
-
-    // We attach the following fields to the SELECT containing a declaration body.
-    String ELEMENT_LABEL_FIELD_NAME = "Label";
-
-    Expression getExpression();
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/PathRecord.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/PathRecord.java
deleted file mode 100644
index 6f6e074..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/PathRecord.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.record;
-
-import java.util.ArrayList;
-import java.util.List;
-
-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.literal.NullLiteral;
-import org.apache.asterix.lang.common.literal.StringLiteral;
-
-/**
- * Construct the record associated with an edge of a path. For vertices that do not belong to any edge, we build a
- * pseudo-edge record composed of: dangling vertex, NULL, NULL. This pseudo-edge record is also constructed in Neo4J.
- *
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor
- */
-public class PathRecord implements IElementRecord {
-    private final Expression pathExpression;
-
-    // A path record is composed of the following fields.
-    public static final String EDGE_FIELD_NAME = "Edge";
-    public static final String LEFT_VERTEX_FIELD_NAME = "LeftVertex";
-    public static final String RIGHT_VERTEX_FIELD_NAME = "RightVertex";
-
-    public PathRecord(Expression leftVertexExpr, Expression edgeExpr, Expression rightVertexExpr) {
-        List<FieldBinding> bindings = new ArrayList<>();
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(LEFT_VERTEX_FIELD_NAME)), leftVertexExpr));
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(EDGE_FIELD_NAME)), edgeExpr));
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(RIGHT_VERTEX_FIELD_NAME)), rightVertexExpr));
-        this.pathExpression = new RecordConstructor(bindings);
-    }
-
-    public PathRecord(Expression leftVertexExpr) {
-        Expression rightVertexExpr = new LiteralExpr(NullLiteral.INSTANCE);
-        Expression edgeExpr = new LiteralExpr(NullLiteral.INSTANCE);
-
-        // Build our path expression.
-        List<FieldBinding> bindings = new ArrayList<>();
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(LEFT_VERTEX_FIELD_NAME)), leftVertexExpr));
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(EDGE_FIELD_NAME)), edgeExpr));
-        bindings.add(new FieldBinding(new LiteralExpr(new StringLiteral(RIGHT_VERTEX_FIELD_NAME)), rightVertexExpr));
-        this.pathExpression = new RecordConstructor(bindings);
-    }
-
-    @Override
-    public Expression getExpression() {
-        return pathExpression;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/VertexRecord.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/VertexRecord.java
deleted file mode 100644
index e8b613c..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/VertexRecord.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.record;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSelectBlocksWithDecls;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildSelectWithSelectBlockStream;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-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.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
-
-/**
- * Lowered representation of a query vertex, constructed with the following process:
- * 1. For each label of the vertex, perform a UNION-ALL of their respective normalized bodies.
- * 2. Build a SELECT-BLOCK for each vertex label, using the result of step 1. For each SELECT-CLAUSE, attach the vertex
- * label and access to the primary key, in addition to the expression from step 1.
- * 3. Now having a list of SELECT-BLOCKs, UNION-ALL each SELECT-BLOCK and build a single SELECT-EXPR. This is the
- * lowered representation of our vertex.
- */
-public class VertexRecord implements IElementRecord {
-    // The details surrounding our vertex will be included in the following field (alongside our body).
-    public static final String VERTEX_DETAIL_NAME = "_GraphixVertexDetail";
-
-    // We attach the following fields to the SELECT containing a declaration body.
-    public static final String PRIMARY_KEY_FIELD_NAME = "PrimaryKey";
-
-    private final List<GraphElementIdentifier> elementIdentifiers;
-    private final VertexPatternExpr vertexPatternExpr;
-    private final Expression vertexExpression;
-    private final VariableExpr vertexVar;
-
-    public VertexRecord(VertexPatternExpr vertexPatternExpr, GraphIdentifier graphIdentifier,
-            LowerSupplierContext lowerSupplierContext) {
-        this.elementIdentifiers = vertexPatternExpr.generateIdentifiers(graphIdentifier);
-        this.vertexVar = new VariableExpr(vertexPatternExpr.getVariableExpr().getVar());
-        this.vertexPatternExpr = vertexPatternExpr;
-
-        // Build the SELECT-EXPR associated with the given element identifiers.
-        ElementLookupTable<GraphElementIdentifier> elementLookupTable = lowerSupplierContext.getElementLookupTable();
-        List<GraphElementDecl> graphElementDeclList =
-                elementIdentifiers.stream().map(elementLookupTable::getElementDecl).collect(Collectors.toList());
-        List<SelectBlock> selectBlockList = buildSelectBlocksWithDecls(graphElementDeclList, vertexVar, d -> {
-            List<Projection> projectionList = new ArrayList<>();
-
-            // Build the label binding for the element detail projection.
-            List<FieldBinding> elementDetailBindings = new ArrayList<>();
-            StringLiteral labelLiteral = new StringLiteral(d.getIdentifier().getElementLabel().toString());
-            LiteralExpr labelFieldValue = new LiteralExpr(labelLiteral);
-            LiteralExpr labelFieldName = new LiteralExpr(new StringLiteral(ELEMENT_LABEL_FIELD_NAME));
-            elementDetailBindings.add(new FieldBinding(labelFieldName, labelFieldValue));
-            RecordConstructor elementDetailRecord = new RecordConstructor(elementDetailBindings);
-            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, elementDetailRecord, ELEMENT_DETAIL_NAME));
-
-            // Build the vertex detail projection.
-            List<FieldBinding> vertexDetailBindings = new ArrayList<>();
-            List<FieldBinding> primaryKeyBindings =
-                    buildAccessorList(vertexVar, elementLookupTable.getVertexKey(d.getIdentifier())).stream()
-                            .map(LowerRewritingUtil::buildFieldBindingFromFieldAccessor).collect(Collectors.toList());
-            RecordConstructor primaryKeyAccess = new RecordConstructor(primaryKeyBindings);
-            LiteralExpr primaryKeyName = new LiteralExpr(new StringLiteral(PRIMARY_KEY_FIELD_NAME));
-            vertexDetailBindings.add(new FieldBinding(primaryKeyName, primaryKeyAccess));
-            RecordConstructor vertexDetailRecord = new RecordConstructor(vertexDetailBindings);
-            projectionList.add(new Projection(Projection.Kind.NAMED_EXPR, vertexDetailRecord, VERTEX_DETAIL_NAME));
-
-            // Return our newly assembled projections.
-            return projectionList;
-        });
-        this.vertexExpression = buildSelectWithSelectBlockStream(selectBlockList.stream());
-    }
-
-    @Override
-    public Expression getExpression() {
-        return vertexExpression;
-    }
-
-    public List<GraphElementIdentifier> getElementIdentifiers() {
-        return elementIdentifiers;
-    }
-
-    public VariableExpr getVarExpr() {
-        return vertexVar;
-    }
-
-    public VertexPatternExpr getVertexPatternExpr() {
-        return vertexPatternExpr;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java
index c6e7043..aa939fb 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/rewrites/resolve/IGraphElementResolver.java
@@ -20,9 +20,10 @@
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.rewrites.visitor.StructureResolutionVisitor;
 
 /**
- * @see org.apache.asterix.graphix.lang.rewrites.visitor.ElementResolutionVisitor
+ * @see StructureResolutionVisitor
  */
 public interface IGraphElementResolver {
     /**
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
index c2035cb..1d558fb 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
@@ -27,6 +27,7 @@
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixDeepCopyVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.LabelConsistencyVisitor;
 import org.apache.asterix.graphix.lang.rewrites.visitor.QueryKnowledgeVisitor;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
@@ -39,11 +40,13 @@
     public static final String METADATA_CONFIG_NAME = "inference-based";
 
     private final QueryKnowledgeVisitor queryKnowledgeVisitor;
+    private final GraphixDeepCopyVisitor graphixDeepCopyVisitor;
     private final SchemaKnowledgeTable schemaKnowledgeTable;
     private boolean isAtFixedPoint = false;
 
     public InferenceBasedResolver(SchemaKnowledgeTable schemaKnowledgeTable) {
         this.queryKnowledgeVisitor = new QueryKnowledgeVisitor();
+        this.graphixDeepCopyVisitor = new GraphixDeepCopyVisitor();
         this.schemaKnowledgeTable = schemaKnowledgeTable;
     }
 
@@ -66,7 +69,12 @@
         }
     }
 
-    private boolean resolveEdge(EdgePatternExpr edgePatternExpr) {
+    /**
+     * Attempt to resolve the unknowns in an {@link EdgePatternExpr}. This includes the labels of the contained
+     * vertices and edges, as well as the edge direction.
+     * @return True if no new information was added. False otherwise.
+     */
+    private boolean resolveEdge(EdgePatternExpr edgePatternExpr) throws CompilationException {
         EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
         if (edgeDescriptor.getPatternType() == EdgeDescriptor.PatternType.PATH) {
             VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
@@ -105,12 +113,14 @@
 
         if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.UNDIRECTED) {
             // We have an undirected edge. Recurse with a LEFT_TO_RIGHT edge...
-            edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT);
-            boolean isLeftToRightModified = !resolveEdge(edgePatternExpr);
+            EdgePatternExpr leftToRightEdgePatternExpr = graphixDeepCopyVisitor.visit(edgePatternExpr, null);
+            leftToRightEdgePatternExpr.getEdgeDescriptor().setEdgeDirection(EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT);
+            boolean isLeftToRightModified = !resolveEdge(leftToRightEdgePatternExpr);
 
             // ...and a RIGHT_TO_LEFT edge.
-            edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT);
-            boolean isRightToLeftModified = !resolveEdge(edgePatternExpr);
+            EdgePatternExpr rightToLeftEdgePatternExpr = graphixDeepCopyVisitor.visit(edgePatternExpr, null);
+            rightToLeftEdgePatternExpr.getEdgeDescriptor().setEdgeDirection(EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT);
+            boolean isRightToLeftModified = !resolveEdge(rightToLeftEdgePatternExpr);
 
             // Determine the direction of our edge, if possible.
             if (isLeftToRightModified && !isRightToLeftModified) {
@@ -122,6 +132,20 @@
             } else {
                 edgeDescriptor.setEdgeDirection(EdgeDescriptor.EdgeDirection.UNDIRECTED);
             }
+
+            // Propagate our label sets.
+            VertexPatternExpr leftVertexExpr = edgePatternExpr.getLeftVertex();
+            VertexPatternExpr rightVertexExpr = edgePatternExpr.getRightVertex();
+            if (isLeftToRightModified) {
+                edgeDescriptor.getEdgeLabels().addAll(leftToRightEdgePatternExpr.getEdgeDescriptor().getEdgeLabels());
+                leftVertexExpr.getLabels().addAll(leftToRightEdgePatternExpr.getLeftVertex().getLabels());
+                rightVertexExpr.getLabels().addAll(leftToRightEdgePatternExpr.getRightVertex().getLabels());
+            }
+            if (isRightToLeftModified) {
+                edgeDescriptor.getEdgeLabels().addAll(rightToLeftEdgePatternExpr.getEdgeDescriptor().getEdgeLabels());
+                leftVertexExpr.getLabels().addAll(rightToLeftEdgePatternExpr.getLeftVertex().getLabels());
+                rightVertexExpr.getLabels().addAll(rightToLeftEdgePatternExpr.getRightVertex().getLabels());
+            }
             return !(isLeftToRightModified || isRightToLeftModified);
         }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java
deleted file mode 100644
index 4625507..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.resolve;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.graphix.lang.clause.FromGraphClause;
-import org.apache.asterix.graphix.lang.rewrites.visitor.LabelConsistencyVisitor;
-import org.apache.asterix.graphix.lang.rewrites.visitor.QueryKnowledgeVisitor;
-
-/**
- * No-resolution strategy. Do not attempt to infer any labels or edge directions (only perform label consistency).
- */
-public class NoResolutionResolver implements IGraphElementResolver {
-    public static final String METADATA_CONFIG_NAME = "no-resolution";
-    public boolean isUnificationPerformed = false;
-
-    @Override
-    public void resolve(FromGraphClause fromGraphClause) throws CompilationException {
-        QueryKnowledgeVisitor queryKnowledgeVisitor = new QueryKnowledgeVisitor();
-        queryKnowledgeVisitor.visit(fromGraphClause, null);
-        new LabelConsistencyVisitor(queryKnowledgeVisitor.getQueryKnowledgeTable()).visit(fromGraphClause, null);
-        isUnificationPerformed = true;
-    }
-
-    @Override
-    public boolean isAtFixedPoint() {
-        return isUnificationPerformed;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.java
deleted file mode 100644
index 506ee13..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.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.rewrites.util;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.Literal;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.struct.OperatorType;
-
-public final class ClauseRewritingUtil {
-    public static Expression buildConnectedClauses(List<Expression> clauses, OperatorType connector) {
-        List<Expression> nonTrueClauses = clauses.stream().filter(e -> {
-            if (e.getKind() == Expression.Kind.LITERAL_EXPRESSION) {
-                LiteralExpr literalExpr = (LiteralExpr) e;
-                return literalExpr.getValue().getLiteralType() != Literal.Type.TRUE;
-            }
-            return true;
-        }).collect(Collectors.toList());
-        switch (nonTrueClauses.size()) {
-            case 0:
-                // We only have a TRUE clause. Return this.
-                return clauses.get(0);
-            case 1:
-                // We do not need to connect a single clause.
-                return nonTrueClauses.get(0);
-            default:
-                // Otherwise, connect all non-true clauses.
-                return new OperatorExpr(nonTrueClauses, Collections.nCopies(clauses.size() - 1, connector), false);
-        }
-    }
-
-    public static Expression appendConnectedClauses(Expression existingClause, List<Expression> clauses,
-            OperatorType connector) {
-        OperatorExpr resultantExpr = new OperatorExpr();
-        for (Expression isomorphismConjunct : clauses) {
-            resultantExpr.addOperand(isomorphismConjunct);
-            resultantExpr.addOperator(connector);
-        }
-        // We use the last connector from before to connect our existing clause.
-        resultantExpr.addOperand(existingClause);
-        return resultantExpr;
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/EdgeRewritingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/EdgeRewritingUtil.java
deleted file mode 100644
index e289119..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/EdgeRewritingUtil.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.util;
-
-import static org.apache.asterix.graphix.lang.rewrites.util.ClauseRewritingUtil.buildConnectedClauses;
-import static org.apache.asterix.graphix.lang.rewrites.util.LowerRewritingUtil.buildAccessorList;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.BiFunction;
-import java.util.function.Function;
-
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierNode;
-import org.apache.asterix.graphix.lang.rewrites.record.IElementRecord;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.OperatorExpr;
-import org.apache.asterix.lang.common.literal.StringLiteral;
-import org.apache.asterix.lang.common.literal.TrueLiteral;
-import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.common.struct.OperatorType;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-
-public final class EdgeRewritingUtil {
-    public static Expression buildVertexEdgeJoin(List<GraphElementIdentifier> vertexIdentifiers,
-            List<GraphElementIdentifier> edgeIdentifiers,
-            Function<GraphElementIdentifier, List<Expression>> edgeLabelPredicateBuilder,
-            BiFunction<GraphElementIdentifier, GraphElementIdentifier, Boolean> elementRestrictionPredicate,
-            Function<GraphElementIdentifier, List<FieldAccessor>> vertexKeyBuilder,
-            Function<GraphElementIdentifier, List<List<FieldAccessor>>> edgeKeysBuilder) {
-        // Connect multiple vertex labels to multiple edge bodies of multiple labels.
-        Expression intermediateClause;
-        List<Expression> finalClauses = new ArrayList<>();
-        for (GraphElementIdentifier edgeIdentifier : edgeIdentifiers) {
-
-            // Connect multiple vertex labels to multiple edge bodies of a single label.
-            List<Expression> multipleVertexToMultipleEdgeClauses = new ArrayList<>();
-            List<List<FieldAccessor>> edgeKeyList = edgeKeysBuilder.apply(edgeIdentifier);
-            for (List<FieldAccessor> edgeKey : edgeKeyList) {
-
-                // Connect multiple vertex labels to a single edge body of a single label.
-                List<Expression> multipleVertexToSingleEdgeClauses = new ArrayList<>();
-                for (GraphElementIdentifier vertexIdentifier : vertexIdentifiers) {
-                    if (!elementRestrictionPredicate.apply(vertexIdentifier, edgeIdentifier)) {
-                        continue;
-                    }
-
-                    // Connect a single vertex label to a single edge body of a single label.
-                    List<Expression> singleVertexToSingleEdgeClauses = new ArrayList<>();
-                    List<FieldAccessor> vertexKey = vertexKeyBuilder.apply(vertexIdentifier);
-                    for (int i = 0; i < edgeKey.size(); i++) {
-                        OperatorExpr equalityExpr = new OperatorExpr(List.of(edgeKey.get(i), vertexKey.get(i)),
-                                Collections.singletonList(OperatorType.EQ), false);
-                        singleVertexToSingleEdgeClauses.add(equalityExpr);
-                    }
-                    singleVertexToSingleEdgeClauses.addAll(edgeLabelPredicateBuilder.apply(edgeIdentifier));
-
-                    intermediateClause = buildConnectedClauses(singleVertexToSingleEdgeClauses, OperatorType.AND);
-                    multipleVertexToSingleEdgeClauses.add(intermediateClause);
-                }
-                if (multipleVertexToSingleEdgeClauses.isEmpty()) {
-                    // If we have no vertices-to-edge clauses, then our intermediate clause is TRUE.
-                    multipleVertexToMultipleEdgeClauses.add(new LiteralExpr(TrueLiteral.INSTANCE));
-
-                } else {
-                    intermediateClause = buildConnectedClauses(multipleVertexToSingleEdgeClauses, OperatorType.OR);
-                    multipleVertexToMultipleEdgeClauses.add(intermediateClause);
-                }
-            }
-
-            intermediateClause = buildConnectedClauses(multipleVertexToMultipleEdgeClauses, OperatorType.OR);
-            finalClauses.add(intermediateClause);
-        }
-
-        return buildConnectedClauses(finalClauses, OperatorType.OR);
-    }
-
-    public static Expression buildInputAssemblyJoin(LowerSupplierNode inputNode, Expression startingAccessExpr,
-            Expression qualifiedVar, Expression existingJoin, Function<String, Boolean> joinFieldPredicate) {
-        boolean isAnyProjectionApplicable =
-                inputNode.getProjectionList().stream().map(Projection::getName).anyMatch(joinFieldPredicate::apply);
-
-        if (!isAnyProjectionApplicable && existingJoin == null) {
-            // We cannot join with any of the input projections. Perform a cross product.
-            return new LiteralExpr(TrueLiteral.INSTANCE);
-
-        } else if (!isAnyProjectionApplicable) {
-            // We cannot join with any of the input projections. Return the existing join.
-            return existingJoin;
-
-        } else {
-            // We can perform a join with one (or more) of our input projections.
-            List<Expression> joinsWithInput = new ArrayList<>();
-            if (existingJoin != null) {
-                joinsWithInput.add(existingJoin);
-            }
-
-            for (Projection inputProjection : inputNode.getProjectionList()) {
-                String projectedVarName = inputProjection.getName();
-                if (joinFieldPredicate.apply(projectedVarName)) {
-                    List<List<String>> projectedFieldName = List.of(List.of(projectedVarName));
-                    FieldAccessor inputAccessExpr = buildAccessorList(startingAccessExpr, projectedFieldName).get(0);
-                    List<OperatorType> joinOperator = Collections.singletonList(OperatorType.EQ);
-                    joinsWithInput.add(new OperatorExpr(List.of(qualifiedVar, inputAccessExpr), joinOperator, false));
-                }
-            }
-            return buildConnectedClauses(joinsWithInput, OperatorType.AND);
-        }
-    }
-
-    public static Function<GraphElementIdentifier, List<List<FieldAccessor>>> buildEdgeKeyBuilder(
-            Expression startingExpr, ElementLookupTable<GraphElementIdentifier> elementLookupTable,
-            Function<GraphElementIdentifier, List<List<String>>> keyFieldBuilder) {
-        return identifier -> {
-            List<List<FieldAccessor>> fieldAccessorList = new ArrayList<>();
-            for (int i = 0; i < elementLookupTable.getEdgeSourceKeys(identifier).size(); i++) {
-                List<List<String>> keyField = keyFieldBuilder.apply(identifier);
-                fieldAccessorList.add(buildAccessorList(startingExpr, keyField));
-            }
-            return fieldAccessorList;
-        };
-    }
-
-    public static Function<GraphElementIdentifier, List<Expression>> buildEdgeLabelPredicateBuilder(
-            Expression vertexExpr, Expression edgeExpr, Function<GraphElementIdentifier, ElementLabel> labelGetter) {
-        return identifier -> {
-            final Identifier elementDetailSuffix = new Identifier(IElementRecord.ELEMENT_DETAIL_NAME);
-            final Identifier elementLabelSuffix = new Identifier(IElementRecord.ELEMENT_LABEL_FIELD_NAME);
-
-            // Access the labels in our vertex and our edge.
-            LiteralExpr vertexValue = new LiteralExpr(new StringLiteral(labelGetter.apply(identifier).toString()));
-            LiteralExpr edgeValue = new LiteralExpr(new StringLiteral(identifier.getElementLabel().toString()));
-            FieldAccessor vertexDetailAccess = new FieldAccessor(vertexExpr, elementDetailSuffix);
-            FieldAccessor edgeDetailAccess = new FieldAccessor(edgeExpr, elementDetailSuffix);
-            FieldAccessor vertexLabelAccess = new FieldAccessor(vertexDetailAccess, elementLabelSuffix);
-            FieldAccessor edgeLabelAccess = new FieldAccessor(edgeDetailAccess, elementLabelSuffix);
-
-            // Ensure that our vertex label and our edge label match.
-            List<Expression> labelPredicates = new ArrayList<>();
-            List<Expression> vertexExprList = List.of(vertexValue, vertexLabelAccess);
-            List<Expression> edgeExprList = List.of(edgeValue, edgeLabelAccess);
-            labelPredicates.add(new OperatorExpr(vertexExprList, List.of(OperatorType.EQ), false));
-            labelPredicates.add(new OperatorExpr(edgeExprList, List.of(OperatorType.EQ), false));
-            return labelPredicates;
-        };
-    }
-}
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/rewrites/util/LowerRewritingUtil.java
index dd07c3e..616138e 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/rewrites/util/LowerRewritingUtil.java
@@ -21,41 +21,48 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.FieldBinding;
-import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.literal.StringLiteral;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
 import org.apache.asterix.lang.common.struct.Identifier;
-import org.apache.asterix.lang.sqlpp.clause.FromClause;
-import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-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.SelectElement;
-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.SetOpType;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
-import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
-import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
 
 public final class LowerRewritingUtil {
-    public static List<FieldAccessor> buildAccessorList(Expression startingExpr, List<List<String>> fieldNames) {
+    public static Expression buildConnectedClauses(List<Expression> clauses, OperatorType connector) {
+        switch (clauses.size()) {
+            case 0:
+                // We must be given a non-empty list.
+                throw new IllegalArgumentException("Given non-empty set of clauses.");
+
+            case 1:
+                // We do not need to connect a single clause.
+                return clauses.get(0);
+
+            default:
+                // Otherwise, connect all non-true clauses.
+                return new OperatorExpr(clauses, Collections.nCopies(clauses.size() - 1, connector), false);
+        }
+    }
+
+    public static Expression buildVertexEdgeJoin(List<FieldAccessor> vertexKey, List<FieldAccessor> edgeKey) {
+        List<Expression> joinClauses = new ArrayList<>();
+        for (int i = 0; i < edgeKey.size(); i++) {
+            OperatorExpr equalityExpr = new OperatorExpr(List.of(edgeKey.get(i), vertexKey.get(i)),
+                    Collections.singletonList(OperatorType.EQ), false);
+            joinClauses.add(equalityExpr);
+        }
+        return buildConnectedClauses(joinClauses, OperatorType.AND);
+    }
+
+    public static List<FieldAccessor> buildAccessorList(Expression startingExpr, List<List<String>> fieldNames)
+            throws CompilationException {
         List<FieldAccessor> fieldAccessors = new ArrayList<>();
         for (List<String> nestedField : fieldNames) {
-            FieldAccessor workingAccessor = new FieldAccessor(startingExpr, new Identifier(nestedField.get(0)));
+            Expression copiedStartingExpr = (Expression) SqlppRewriteUtil.deepCopy(startingExpr);
+            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));
             }
@@ -63,93 +70,4 @@
         }
         return fieldAccessors;
     }
-
-    public static FieldBinding buildFieldBindingFromFieldAccessor(FieldAccessor fieldAccessor) {
-        LiteralExpr leftExpr = new LiteralExpr(new StringLiteral(fieldAccessor.getIdent().getValue()));
-        return new FieldBinding(leftExpr, fieldAccessor);
-    }
-
-    public static SetOperationInput buildSetOpInputWithFrom(FromTerm term, List<Projection> projections,
-            List<AbstractClause> letWhereClauseList) {
-        FromClause fromClause = new FromClause(Collections.singletonList(term));
-
-        // Build up each FROM-CLAUSE as its own SET-OP-INPUT.
-        SelectRegular selectRegular = new SelectRegular(projections);
-        SelectClause selectClause = new SelectClause(null, selectRegular, false);
-        SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, letWhereClauseList, null, null);
-        return new SetOperationInput(selectBlock, null);
-    }
-
-    public static SelectExpression buildSelectWithFromAndElement(FromTerm fromTerm,
-            List<AbstractClause> letWhereClauses, Expression element) {
-        FromClause fromClause = new FromClause(List.of(fromTerm));
-        SelectElement selectElement = new SelectElement(element);
-        SelectClause selectClause = new SelectClause(selectElement, null, false);
-        SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, letWhereClauses, null, null);
-        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
-        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
-        return new SelectExpression(null, selectSetOperation, null, null, true);
-    }
-
-    public static LetClause buildLetClauseWithFromList(List<FromTerm> fromTerms, List<AbstractClause> letWhereClauses,
-            List<Projection> projections, VariableExpr bindingLetVar) {
-        // Build up a LET-CLAUSE from our input FROM-TERM list. Use a SELECT-ELEMENT instead of a SELECT-REGULAR.
-        FromClause fromClause = new FromClause(fromTerms);
-        SelectRegular selectRegular = new SelectRegular(projections);
-        SelectClause selectClause = new SelectClause(null, selectRegular, false);
-        SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, letWhereClauses, null, null);
-        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
-        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
-        SelectExpression selectExpression = new SelectExpression(null, selectSetOperation, null, null, true);
-        return new LetClause(bindingLetVar, selectExpression);
-    }
-
-    public static List<SelectBlock> buildSelectBlocksWithDecls(List<GraphElementDecl> graphElementDecls,
-            VariableExpr startingVar, Function<GraphElementDecl, List<Projection>> detailSupplier) {
-        // Each declaration body will act as its own SELECT-BLOCK.
-        List<SelectBlock> selectBlockList = new ArrayList<>();
-        for (GraphElementDecl graphElementDecl : graphElementDecls) {
-            for (Expression normalizedBody : graphElementDecl.getNormalizedBodies()) {
-                FromTerm fromTerm = new FromTerm(normalizedBody, startingVar, null, null);
-                FromClause fromClause = new FromClause(Collections.singletonList(fromTerm));
-
-                // All details should be given to us as another set of projections.
-                Expression selectElementExpr = detailSupplier.apply(graphElementDecl).stream().sequential()
-                        .reduce(startingVar, (Expression inputExpr, Projection nextProjection) -> {
-                            FunctionSignature functionSignature = new FunctionSignature(BuiltinFunctions.RECORD_ADD);
-                            LiteralExpr fieldNameExpr = new LiteralExpr(new StringLiteral(nextProjection.getName()));
-                            Expression fieldValueExpr = nextProjection.getExpression();
-                            List<Expression> callArgumentList = List.of(inputExpr, fieldNameExpr, fieldValueExpr);
-                            return new CallExpr(functionSignature, callArgumentList);
-                        }, (a, b) -> b);
-                SelectElement selectElement = new SelectElement(selectElementExpr);
-
-                // Build up each FROM-CLAUSE as its own SELECT-BLOCK.
-                SelectClause selectClause = new SelectClause(selectElement, null, false);
-                SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, null, null, null);
-                selectBlockList.add(selectBlock);
-            }
-        }
-
-        return selectBlockList;
-    }
-
-    public static SelectExpression buildSelectWithSelectBlockStream(Stream<SelectBlock> selectBlockStream) {
-        // Build our parts, with each SELECT-BLOCK acting as its own SET-OPERATION-INPUT.
-        List<SetOperationInput> selectExprParts =
-                selectBlockStream.map(b -> new SetOperationInput(b, null)).collect(Collectors.toList());
-
-        // Next, build our SELECT-SET-OPERATION. The first part will act as our leading term.
-        SetOperationInput leadingPart = selectExprParts.get(0);
-        List<SetOperationRight> rightPart = new ArrayList<>();
-        if (selectExprParts.size() > 1) {
-            for (SetOperationInput trailingPart : selectExprParts.subList(1, selectExprParts.size())) {
-                rightPart.add(new SetOperationRight(SetOpType.UNION, false, trailingPart));
-            }
-        }
-        SelectSetOperation selectExprComposite = new SelectSetOperation(leadingPart, rightPart);
-
-        // Finally, build our SelectExpression.
-        return new SelectExpression(null, selectExprComposite, null, null, true);
-    }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
index 83d35c2..8fdc0da 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
@@ -27,8 +27,9 @@
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 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.GraphElementDecl;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
@@ -37,7 +38,7 @@
 import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
 
-public class AbstractGraphixQueryVisitor extends AbstractSqlppSimpleExpressionVisitor
+public abstract class AbstractGraphixQueryVisitor extends AbstractSqlppSimpleExpressionVisitor
         implements IGraphixLangVisitor<Expression, ILangExpression> {
     @Override
     public Expression visit(GraphConstructor gc, ILangExpression arg) throws CompilationException {
@@ -152,7 +153,12 @@
     }
 
     @Override
-    public Expression visit(GraphElementDecl gel, ILangExpression arg) throws CompilationException {
+    public Expression visit(GraphElementDeclaration gel, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(DeclareGraphStatement dgs, ILangExpression arg) throws CompilationException {
         return null;
     }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java
new file mode 100644
index 0000000..b7b1b33
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/CanonicalExpansionVisitor.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.rewrites.canonical.DanglingVertexExpander;
+import org.apache.asterix.graphix.lang.rewrites.canonical.EdgeSubPathExpander;
+import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+
+/**
+ * Expand a single {@link SelectSetOperation} with {@link VertexPatternExpr} and {@link EdgePatternExpr} nodes into
+ * several UNION-ALL branches of "canonical form". This preprocessing step allows for simpler lowering logic (and
+ * therefore, potentially more efficient plans) at the cost of a wider AST.
+ *
+ * @see DanglingVertexExpander
+ * @see EdgeSubPathExpander
+ */
+public class CanonicalExpansionVisitor extends AbstractGraphixQueryVisitor {
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final Deque<CanonicalPatternEnvironment> environmentStack;
+    private final DanglingVertexExpander danglingVertexExpander;
+    private final EdgeSubPathExpander edgeSubPathExpander;
+
+    // To avoid "over-expanding", we require knowledge of our schema.
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
+    private final MetadataProvider metadataProvider;
+
+    private static class CanonicalPatternEnvironment {
+        private List<GraphSelectBlock> graphSelectBlockList;
+        private List<SetOperationInput> setOperationInputList;
+        private SchemaKnowledgeTable schemaKnowledgeTable;
+        private boolean wasExpanded = false;
+
+        // The following is collected for our post-canonicalization pass.
+        private GraphSelectBlock selectBlockExpansionSource;
+        private Set<SetOperationInput> generatedSetInputs;
+        private List<VarIdentifier> userLiveVariables;
+    }
+
+    public CanonicalExpansionVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
+        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
+        this.danglingVertexExpander = new DanglingVertexExpander();
+        this.edgeSubPathExpander = new EdgeSubPathExpander(graphixRewritingContext::getNewGraphixVariable);
+        this.graphixRewritingContext = graphixRewritingContext;
+
+        // Keep an empty environment in the stack, in case we have a non-SELECT expression.
+        this.environmentStack = new ArrayDeque<>();
+        this.environmentStack.addLast(new CanonicalPatternEnvironment());
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        environmentStack.addLast(new CanonicalPatternEnvironment());
+        super.visit(selectExpression, arg);
+
+        // If expansion has occurred, we need to perform clean-up if we have output modifiers / grouping.
+        CanonicalPatternEnvironment workingEnvironment = environmentStack.removeLast();
+        SelectExpression workingSelectExpression = selectExpression;
+        if (workingEnvironment.wasExpanded && (selectExpression.hasLimit() || selectExpression.hasOrderby()
+                || workingEnvironment.graphSelectBlockList.stream().anyMatch(SelectBlock::hasGroupbyClause))) {
+            PostCanonicalizationVisitor postCanonicalizationVisitor = new PostCanonicalizationVisitor(
+                    graphixRewritingContext, workingEnvironment.selectBlockExpansionSource,
+                    workingEnvironment.generatedSetInputs, workingEnvironment.userLiveVariables);
+            workingSelectExpression = (SelectExpression) postCanonicalizationVisitor.visit(selectExpression, arg);
+        }
+        return workingSelectExpression;
+    }
+
+    @Override
+    public Expression visit(SelectSetOperation selectSetOperation, ILangExpression arg) throws CompilationException {
+        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
+        workingEnvironment.setOperationInputList = new ArrayList<>();
+        selectSetOperation.getLeftInput().accept(this, arg);
+        for (SetOperationRight right : selectSetOperation.getRightInputs()) {
+            right.getSetOperationRightInput().accept(this, arg);
+        }
+        for (int i = 0; i < workingEnvironment.setOperationInputList.size(); i++) {
+            SetOperationInput setOperationInput = workingEnvironment.setOperationInputList.get(i);
+            if (i == 0) {
+                selectSetOperation.getLeftInput().setSelectBlock(setOperationInput.getSelectBlock());
+                selectSetOperation.getLeftInput().setSubquery(setOperationInput.getSubquery());
+
+            } else if (selectSetOperation.getRightInputs().size() > i) {
+                SetOperationInput setOperationRightInput =
+                        selectSetOperation.getRightInputs().get(i - 1).getSetOperationRightInput();
+                setOperationRightInput.setSelectBlock(setOperationInput.getSelectBlock());
+                setOperationRightInput.setSubquery(setOperationRightInput.getSubquery());
+
+            } else {
+                SetOperationRight setOperationRight = new SetOperationRight(SetOpType.UNION, false, setOperationInput);
+                selectSetOperation.getRightInputs().add(setOperationRight);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
+        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
+        workingEnvironment.graphSelectBlockList = new ArrayList<>();
+        workingEnvironment.graphSelectBlockList.add(graphSelectBlock);
+        super.visit(graphSelectBlock, arg);
+
+        if (workingEnvironment.wasExpanded) {
+            // If we have expanded, then we need to keep track of this GRAPH-SELECT-BLOCK.
+            workingEnvironment.selectBlockExpansionSource = new GraphixDeepCopyVisitor().visit(graphSelectBlock, null);
+
+            // We also need to collect all live variables up to this point.
+            workingEnvironment.userLiveVariables = new ArrayList<>();
+            for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
+                for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                    if (pathExpression.getVariableExpr() != null) {
+                        workingEnvironment.userLiveVariables.add(pathExpression.getVariableExpr().getVar());
+                    }
+                    for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                        VarIdentifier vertexVariable = vertexExpression.getVariableExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(vertexVariable)) {
+                            workingEnvironment.userLiveVariables.add(vertexVariable);
+                        }
+                    }
+                    for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                        VarIdentifier edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(edgeVariable)) {
+                            workingEnvironment.userLiveVariables.add(edgeVariable);
+                        }
+                    }
+                }
+            }
+            if (!graphSelectBlock.getFromGraphClause().getCorrelateClauses().isEmpty()) {
+                FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
+                List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
+                for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
+                    VarIdentifier bindingVariable = correlateClause.getRightVariable().getVar();
+                    if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
+                        workingEnvironment.userLiveVariables.add(bindingVariable);
+                    }
+                }
+            }
+            if (graphSelectBlock.hasLetWhereClauses()) {
+                for (AbstractClause abstractClause : graphSelectBlock.getLetWhereList()) {
+                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) abstractClause;
+                        VarIdentifier bindingVariable = letClause.getVarExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
+                            workingEnvironment.userLiveVariables.add(bindingVariable);
+                        }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
+
+        // Establish our schema knowledge.
+        if (fromGraphClause.getGraphConstructor() == null) {
+            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
+                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
+            Identifier graphName = fromGraphClause.getGraphName();
+
+            // First, try to find our graph inside our declared graph set.
+            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
+            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
+            if (declaredGraph != null) {
+                workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(declaredGraph.getGraphConstructor());
+
+            } else {
+                // Otherwise, fetch the graph from our metadata.
+                try {
+                    Graph graphFromMetadata =
+                            getGraph(metadataProvider.getMetadataTxnContext(), dataverseName, graphName.getValue());
+                    if (graphFromMetadata == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                                "Graph " + graphName.getValue() + " does not exist.");
+                    }
+                    workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
+
+                } catch (AlgebricksException e) {
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                            "Graph " + graphName.getValue() + " does not exist.");
+                }
+            }
+
+        } else {
+            workingEnvironment.schemaKnowledgeTable = new SchemaKnowledgeTable(fromGraphClause.getGraphConstructor());
+        }
+        super.visit(fromGraphClause, arg);
+
+        // Create SOI from our GSBs back to our immediate ancestor SELECT-EXPR.
+        if (workingEnvironment.graphSelectBlockList.size() > 1) {
+            workingEnvironment.generatedSetInputs = new HashSet<>();
+            workingEnvironment.wasExpanded = true;
+        }
+        for (GraphSelectBlock graphSelectBlock : workingEnvironment.graphSelectBlockList) {
+            SetOperationInput setOperationInput = new SetOperationInput(graphSelectBlock, null);
+            workingEnvironment.setOperationInputList.add(setOperationInput);
+            if (workingEnvironment.wasExpanded) {
+                workingEnvironment.generatedSetInputs.add(setOperationInput);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg) throws CompilationException {
+        CanonicalPatternEnvironment workingEnvironment = environmentStack.getLast();
+        Set<VertexPatternExpr> connectedVertices = new HashSet<>();
+        for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
+            connectedVertices.add(edgeExpression.getLeftVertex());
+            connectedVertices.add(edgeExpression.getRightVertex());
+            edgeSubPathExpander.resetSchema(workingEnvironment.schemaKnowledgeTable);
+            edgeSubPathExpander.apply(edgeExpression, workingEnvironment.graphSelectBlockList);
+        }
+        for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
+            if (!connectedVertices.contains(vertexExpression)) {
+                danglingVertexExpander.apply(vertexExpression, workingEnvironment.graphSelectBlockList);
+            }
+        }
+        return pathPatternExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java
new file mode 100644
index 0000000..448f698
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementBodyAnalysisVisitor.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import 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.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.LiteralExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.literal.StringLiteral;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+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.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.util.FunctionMapUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+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.
+ */
+public class ElementBodyAnalysisVisitor extends AbstractSqlppSimpleExpressionVisitor {
+    private final ElementBodyAnalysisContext elementBodyAnalysisContext = new ElementBodyAnalysisContext();
+
+    public static class ElementBodyAnalysisContext {
+        private List<AbstractBinaryCorrelateClause> unnestClauses = null;
+        private List<LetClause> letClauses = null;
+        private List<WhereClause> whereClauses = null;
+        private List<Projection> selectClauseProjections = null;
+        private VariableExpr fromTermVariable = null;
+        private Expression selectElement = null;
+
+        // At a minimum, this field must be defined.
+        private CallExpr datasetCallExpression = null;
+        private DataverseName dataverseName = null;
+        private String datasetName = null;
+
+        // We take an optimistic approach, and look for cases where we _cannot_ inline our body.
+        private boolean isExpressionInline = true;
+
+        public DataverseName getDataverseName() {
+            return dataverseName;
+        }
+
+        public String getDatasetName() {
+            return datasetName;
+        }
+
+        public boolean isExpressionInline() {
+            return isExpressionInline;
+        }
+
+        public List<LetClause> getLetClauses() {
+            return letClauses;
+        }
+
+        public List<WhereClause> getWhereClauses() {
+            return whereClauses;
+        }
+
+        public List<Projection> getSelectClauseProjections() {
+            return selectClauseProjections;
+        }
+
+        public List<AbstractBinaryCorrelateClause> getUnnestClauses() {
+            return unnestClauses;
+        }
+
+        public VariableExpr getFromTermVariable() {
+            return fromTermVariable;
+        }
+
+        public Expression getSelectElement() {
+            return selectElement;
+        }
+
+        public CallExpr getDatasetCallExpression() {
+            return datasetCallExpression;
+        }
+
+        public boolean isSelectClauseInline() {
+            if (selectElement == null && selectClauseProjections == null) {
+                return true;
+
+            } else if (selectElement != null && selectElement.getKind() == Expression.Kind.VARIABLE_EXPRESSION) {
+                VariableExpr selectElementVariableExpr = (VariableExpr) selectElement;
+                return selectElementVariableExpr.getVar() == fromTermVariable.getVar();
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+        FunctionSignature functionSignature = callExpr.getFunctionSignature();
+        FunctionIdentifier functionIdentifier = functionSignature.createFunctionIdentifier();
+        if (functionIdentifier.equals(BuiltinFunctions.DATASET)) {
+            LiteralExpr dataverseNameExpr = (LiteralExpr) callExpr.getExprList().get(0);
+            LiteralExpr datasetNameExpr = (LiteralExpr) callExpr.getExprList().get(1);
+            String dataverseName = ((StringLiteral) dataverseNameExpr.getValue()).getValue();
+            String datasetName = ((StringLiteral) datasetNameExpr.getValue()).getValue();
+            elementBodyAnalysisContext.datasetCallExpression = callExpr;
+            elementBodyAnalysisContext.dataverseName = DataverseName.createBuiltinDataverseName(dataverseName);
+            elementBodyAnalysisContext.datasetName = datasetName;
+            return callExpr;
+
+        } else if (FunctionMapUtil.isSql92AggregateFunction(functionSignature)
+                || FunctionMapUtil.isCoreAggregateFunction(functionSignature)
+                || BuiltinFunctions.getWindowFunction(functionIdentifier) != null) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return callExpr;
+        }
+        return callExpr;
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        if (arg != null) {
+            // We are not in a top-level SELECT-EXPR. Do not proceed.
+            return selectExpression;
+        }
+        if (selectExpression.hasOrderby() || selectExpression.hasLimit() || selectExpression.hasLetClauses()) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return selectExpression;
+        }
+        selectExpression.getSelectSetOperation().accept(this, selectExpression);
+        return selectExpression;
+    }
+
+    @Override
+    public Expression visit(SelectSetOperation selectSetOperation, ILangExpression arg) throws CompilationException {
+        if (selectSetOperation.hasRightInputs()) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+        }
+        SetOperationInput leftInput = selectSetOperation.getLeftInput();
+        if (leftInput.subquery()) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+        }
+        leftInput.getSelectBlock().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        if (selectBlock.hasGroupbyClause() || selectBlock.hasLetHavingClausesAfterGroupby()) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+        }
+        if (selectBlock.hasFromClause()) {
+            selectBlock.getFromClause().accept(this, arg);
+        }
+        if (selectBlock.hasLetWhereClauses()) {
+            selectBlock.getLetWhereList().forEach(c -> {
+                if (c.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                    if (elementBodyAnalysisContext.letClauses == null) {
+                        elementBodyAnalysisContext.letClauses = new ArrayList<>();
+                    }
+                    elementBodyAnalysisContext.letClauses.add((LetClause) c);
+
+                } else { //c.getClauseType() == Clause.ClauseType.WHERE_CLAUSE
+                    if (elementBodyAnalysisContext.whereClauses == null) {
+                        elementBodyAnalysisContext.whereClauses = new ArrayList<>();
+                    }
+                    elementBodyAnalysisContext.whereClauses.add((WhereClause) c);
+                }
+            });
+        }
+        selectBlock.getSelectClause().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromClause fromClause, ILangExpression arg) throws CompilationException {
+        if (fromClause.getFromTerms().size() > 1) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+        }
+        fromClause.getFromTerms().get(0).accept(this, arg);
+        return null;
+    }
+
+    @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))) {
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+
+        } else if (fromTerm.hasPositionalVariable()) {
+            // TODO (GLENN): Add support for positional variables.
+            elementBodyAnalysisContext.isExpressionInline = false;
+            return null;
+
+        }
+        if (!correlateClauses.isEmpty()) {
+            elementBodyAnalysisContext.unnestClauses = correlateClauses;
+        }
+        fromTerm.getLeftExpression().accept(this, arg);
+        elementBodyAnalysisContext.fromTermVariable = fromTerm.getLeftVariable();
+        return null;
+    }
+
+    @Override
+    public Expression visit(SelectClause selectClause, ILangExpression arg) throws CompilationException {
+        if (selectClause.selectElement()) {
+            elementBodyAnalysisContext.selectElement = selectClause.getSelectElement().getExpression();
+
+        } else if (selectClause.selectRegular()) {
+            SelectRegular selectRegular = selectClause.getSelectRegular();
+            List<Projection> projectionList = selectRegular.getProjections();
+            if (projectionList.stream().anyMatch(p -> p.getKind() != Projection.Kind.NAMED_EXPR)) {
+                elementBodyAnalysisContext.isExpressionInline = false;
+
+            } else {
+                elementBodyAnalysisContext.selectClauseProjections = projectionList;
+                for (Projection projection : projectionList) {
+                    projection.getExpression().accept(this, arg);
+                }
+            }
+        }
+        return null;
+    }
+
+    public ElementBodyAnalysisContext getElementBodyAnalysisContext() throws CompilationException {
+        if (elementBodyAnalysisContext.isExpressionInline
+                && (elementBodyAnalysisContext.datasetName == null || elementBodyAnalysisContext.dataverseName == null
+                        || elementBodyAnalysisContext.datasetCallExpression == null)) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                    "Analysis of inline element body yielded no dataset!");
+        }
+        return elementBodyAnalysisContext;
+    }
+}
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/rewrites/visitor/ElementLookupTableVisitor.java
index c150b62..6ba1d87 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/rewrites/visitor/ElementLookupTableVisitor.java
@@ -18,7 +18,11 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
+import static org.apache.asterix.graphix.lang.parser.GraphElementBodyParser.parse;
+
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -32,10 +36,11 @@
 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.GraphElementBodyParser;
 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.statement.GraphElementDecl;
+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.metadata.entity.schema.Edge;
@@ -49,8 +54,9 @@
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 /**
- * Populate the given graph element table, which will hold all referenced {@link GraphElementDecl}s. We assume that our
- * graph elements are properly labeled at this point (i.e. {@link ElementResolutionVisitor} must run before this).
+ * 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
+ * this).
  */
 public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
     private final IWarningCollector warningCollector;
@@ -59,14 +65,16 @@
 
     private final Set<ElementLabel> referencedVertexLabels = new HashSet<>();
     private final Set<ElementLabel> referencedEdgeLabels = new HashSet<>();
-    private final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
+    private final ElementLookupTable elementLookupTable;
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
 
-    public ElementLookupTableVisitor(ElementLookupTable<GraphElementIdentifier> elementLookupTable,
-            MetadataProvider metadataProvider, GraphixParserFactory parserFactory, IWarningCollector warningCollector) {
-        this.warningCollector = warningCollector;
-        this.parserFactory = parserFactory;
-        this.elementLookupTable = elementLookupTable;
-        this.metadataProvider = metadataProvider;
+    public ElementLookupTableVisitor(GraphixRewritingContext graphixRewritingContext,
+            ElementLookupTable elementLookupTable, GraphixParserFactory parserFactory) {
+        this.parserFactory = Objects.requireNonNull(parserFactory);
+        this.elementLookupTable = Objects.requireNonNull(elementLookupTable);
+        this.warningCollector = graphixRewritingContext.getWarningCollector();
+        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
+        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
     }
 
     @Override
@@ -75,54 +83,64 @@
             m.accept(this, null);
         }
 
-        if (fromGraphClause.getGraphConstructor() == null) {
-            // Our query refers to a named graph. Load this from our metadata.
+        GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
+        GraphIdentifier graphIdentifier = null;
+        if (graphConstructor == null) {
             DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
                     ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
             Identifier graphName = fromGraphClause.getGraphName();
-            Graph graphFromMetadata;
-            try {
-                graphFromMetadata = GraphixMetadataExtension.getGraph(metadataProvider.getMetadataTxnContext(),
-                        dataverseName, graphName.getValue());
-                if (graphFromMetadata == null) {
+
+            // 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();
+
+            } else {
+                // Otherwise, load this from our metadata.
+                Graph graphFromMetadata;
+                try {
+                    graphFromMetadata = GraphixMetadataExtension.getGraph(metadataProvider.getMetadataTxnContext(),
+                            dataverseName, graphName.getValue());
+                    if (graphFromMetadata == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                                "Graph " + graphName.getValue() + " does not exist.");
+                    }
+
+                } catch (AlgebricksException e) {
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
                             "Graph " + graphName.getValue() + " does not exist.");
                 }
 
-            } catch (AlgebricksException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                        "Graph " + graphName.getValue() + " does not exist.");
-            }
-
-            for (Vertex vertex : graphFromMetadata.getGraphSchema().getVertices()) {
-                if (referencedVertexLabels.contains(vertex.getLabel())) {
-                    GraphElementDecl vertexDecl = GraphElementBodyParser.parse(vertex, parserFactory, warningCollector);
-                    elementLookupTable.put(vertex.getIdentifier(), vertexDecl);
-                    elementLookupTable.putVertexKey(vertex.getIdentifier(), vertex.getPrimaryKeyFieldNames());
+                for (Vertex vertex : graphFromMetadata.getGraphSchema().getVertices()) {
+                    if (referencedVertexLabels.contains(vertex.getLabel())) {
+                        GraphElementDeclaration vertexDecl = parse(vertex, parserFactory, warningCollector);
+                        elementLookupTable.put(vertex.getIdentifier(), vertexDecl);
+                        elementLookupTable.putVertexKey(vertex.getIdentifier(), vertex.getPrimaryKeyFieldNames());
+                    }
+                }
+                for (Edge edge : graphFromMetadata.getGraphSchema().getEdges()) {
+                    if (referencedEdgeLabels.contains(edge.getLabel())) {
+                        GraphElementDeclaration edgeDecl = parse(edge, parserFactory, warningCollector);
+                        elementLookupTable.put(edge.getIdentifier(), edgeDecl);
+                        elementLookupTable.putEdgeKeys(edge.getIdentifier(), edge.getSourceKeyFieldNames(),
+                                edge.getDestinationKeyFieldNames());
+                    }
                 }
             }
-            for (Edge edge : graphFromMetadata.getGraphSchema().getEdges()) {
-                if (referencedEdgeLabels.contains(edge.getLabel())) {
-                    GraphElementDecl edgeDecl = GraphElementBodyParser.parse(edge, parserFactory, warningCollector);
-                    elementLookupTable.put(edge.getIdentifier(), edgeDecl);
-                    edge.getDefinitions().forEach(d -> elementLookupTable.putEdgeKeys(edge.getIdentifier(),
-                            d.getSourceKeyFieldNames(), d.getDestinationKeyFieldNames()));
-                    elementLookupTable.putEdgeLabels(edge.getIdentifier(), edge.getSourceLabel(),
-                            edge.getDestinationLabel());
-                }
+        }
+        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());
             }
 
-        } else {
-            // We have been provided an anonymous graph. Load the referenced elements from our walk.
-            GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
-            DataverseName defaultDataverse = metadataProvider.getDefaultDataverse().getDataverseName();
-            GraphIdentifier 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());
-                    elementLookupTable.put(identifier, new GraphElementDecl(identifier, vertex.getExpression()));
+                    elementLookupTable.put(identifier, new GraphElementDeclaration(identifier, vertex.getExpression()));
                     elementLookupTable.putVertexKey(identifier, vertex.getPrimaryKeyFields());
                 }
             }
@@ -130,10 +148,9 @@
                 if (referencedEdgeLabels.contains(edge.getEdgeLabel())) {
                     GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier,
                             GraphElementIdentifier.Kind.EDGE, edge.getEdgeLabel());
-                    elementLookupTable.put(identifier, new GraphElementDecl(identifier, edge.getExpression()));
+                    elementLookupTable.put(identifier, new GraphElementDeclaration(identifier, edge.getExpression()));
                     elementLookupTable.putEdgeKeys(identifier, edge.getSourceKeyFields(),
                             edge.getDestinationKeyFields());
-                    elementLookupTable.putEdgeLabels(identifier, edge.getSourceLabel(), edge.getDestinationLabel());
                 }
             }
         }
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/rewrites/visitor/FunctionResolutionVisitor.java
new file mode 100644
index 0000000..689f58f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/FunctionResolutionVisitor.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.rewrites.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.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppFunctionCallResolverVisitor;
+
+/**
+ * Resolve all function calls, while accounting for Graphix specific functions. This is meant to replace the class
+ * {@link org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppFunctionCallResolverVisitor}, and should contain all
+ * the functionality exposed there.
+ */
+public class FunctionResolutionVisitor extends AbstractGraphixQueryVisitor {
+    private final SqlppFunctionCallResolverVisitor sqlppFunctionCallResolverVisitor;
+    private final GraphixFunctionResolver graphixFunctionResolver;
+    private final boolean allowNonStoredUdfCalls;
+
+    public FunctionResolutionVisitor(GraphixRewritingContext graphixRewritingContext, boolean allowNonStoredUDFCalls) {
+        this.sqlppFunctionCallResolverVisitor =
+                new SqlppFunctionCallResolverVisitor(graphixRewritingContext, allowNonStoredUDFCalls);
+        this.graphixFunctionResolver = new GraphixFunctionResolver(graphixRewritingContext.getMetadataProvider(),
+                graphixRewritingContext.getDeclaredFunctions());
+        this.allowNonStoredUdfCalls = allowNonStoredUDFCalls;
+    }
+
+    @Override
+    public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+        FunctionSignature functionSignature = graphixFunctionResolver.resolve(callExpr, allowNonStoredUdfCalls);
+        if (functionSignature != null) {
+            callExpr.setFunctionSignature(functionSignature);
+        }
+        return super.visit(callExpr, arg);
+    }
+
+    @Override
+    public Expression visit(WindowExpression windowExpression, ILangExpression arg) throws CompilationException {
+        // Delegate WINDOW function resolution to our SQL++ resolver.
+        return sqlppFunctionCallResolverVisitor.visit(windowExpression, arg);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GenerateVariableVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GenerateVariableVisitor.java
deleted file mode 100644
index 5a06aa4..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GenerateVariableVisitor.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * http://www.apache.org/licenses/LICENSE-2.0
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.rewrites.visitor;
-
-import java.util.function.Supplier;
-
-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.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
-
-/**
- * Populate all unknown a) graph elements (vertices and edges), b) column names in SELECT-CLAUSEs, and c) GROUP-BY keys.
- */
-public class GenerateVariableVisitor extends AbstractGraphixQueryVisitor {
-    private final GenerateColumnNameVisitor generateColumnNameVisitor;
-    private final Supplier<VarIdentifier> newVariableSupplier;
-
-    public GenerateVariableVisitor(GraphixRewritingContext graphixRewritingContext) {
-        generateColumnNameVisitor = new GenerateColumnNameVisitor(graphixRewritingContext.getLangRewritingContext());
-        newVariableSupplier = graphixRewritingContext::getNewVariable;
-    }
-
-    @Override
-    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
-        selectExpression.accept(generateColumnNameVisitor, arg);
-        return super.visit(selectExpression, arg);
-    }
-
-    @Override
-    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
-        if (vertexExpression.getVariableExpr() == null) {
-            vertexExpression.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
-        }
-        return super.visit(vertexExpression, arg);
-    }
-
-    @Override
-    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
-        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
-        if (edgeDescriptor.getVariableExpr() == null) {
-            edgeDescriptor.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
-        }
-        return super.visit(edgeExpression, arg);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java
new file mode 100644
index 0000000..8a4b93c
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixDeepCopyVisitor.java
@@ -0,0 +1,211 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+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;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
+import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractClause;
+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.SelectClause;
+import org.apache.asterix.lang.sqlpp.visitor.DeepCopyVisitor;
+
+/**
+ * Extend {@link DeepCopyVisitor} to include Graphix AST nodes.
+ */
+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);
+        GroupbyClause clonedGroupByClause = null;
+        if (graphSelectBlock.hasGroupbyClause()) {
+            clonedGroupByClause = this.visit(graphSelectBlock.getGroupbyClause(), arg);
+        }
+        List<AbstractClause> clonedLetWhereClauses = new ArrayList<>();
+        List<AbstractClause> clonedLetHavingClauses = new ArrayList<>();
+        for (AbstractClause letWhereClause : graphSelectBlock.getLetWhereList()) {
+            clonedLetWhereClauses.add((AbstractClause) letWhereClause.accept(this, arg));
+        }
+        for (AbstractClause letHavingClause : graphSelectBlock.getLetHavingListAfterGroupby()) {
+            clonedLetHavingClauses.add((AbstractClause) letHavingClause.accept(this, arg));
+        }
+        return new GraphSelectBlock(clonedSelectClause, clonedFromGraphClause, clonedLetWhereClauses,
+                clonedGroupByClause, clonedLetHavingClauses);
+    }
+
+    @Override
+    public FromGraphClause visit(FromGraphClause fromGraphClause, Void arg) throws CompilationException {
+        List<AbstractBinaryCorrelateClause> clonedCorrelateClauses = new ArrayList<>();
+        List<MatchClause> clonedMatchClauses = new ArrayList<>();
+        for (AbstractBinaryCorrelateClause correlateClause : fromGraphClause.getCorrelateClauses()) {
+            clonedCorrelateClauses.add((AbstractBinaryCorrelateClause) correlateClause.accept(this, arg));
+        }
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            clonedMatchClauses.add(this.visit(matchClause, arg));
+        }
+        if (fromGraphClause.getGraphConstructor() != null) {
+            GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
+            return new FromGraphClause(graphConstructor, clonedMatchClauses, clonedCorrelateClauses);
+
+        } else {
+            return new FromGraphClause(fromGraphClause.getDataverseName(), fromGraphClause.getGraphName(),
+                    clonedMatchClauses, clonedCorrelateClauses);
+        }
+    }
+
+    @Override
+    public MatchClause visit(MatchClause matchClause, Void arg) throws CompilationException {
+        List<PathPatternExpr> clonedPathExpression = new ArrayList<>();
+        for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+            clonedPathExpression.add(this.visit(pathExpression, arg));
+        }
+        return new MatchClause(clonedPathExpression, matchClause.getMatchType());
+    }
+
+    @Override
+    public PathPatternExpr visit(PathPatternExpr pathPatternExpr, Void arg) throws CompilationException {
+        List<EdgePatternExpr> clonedEdgeExpressions = new ArrayList<>();
+        List<VertexPatternExpr> clonedVertexExpressions = new ArrayList<>();
+        VariableExpr clonedVariableExpr = null;
+        if (pathPatternExpr.getVariableExpr() != null) {
+            clonedVariableExpr = this.visit(pathPatternExpr.getVariableExpr(), arg);
+        }
+
+        // Only visit dangling vertices in our edge.
+        Set<VarIdentifier> 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());
+        }
+        for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
+            if (!visitedVertices.contains(vertexExpression.getVariableExpr().getVar())) {
+                VertexPatternExpr clonedVertexExpression = this.visit(vertexExpression, arg);
+                clonedVertexExpressions.add(clonedVertexExpression);
+                visitedVertices.add(clonedVertexExpression.getVariableExpr().getVar());
+            }
+        }
+
+        // Clone our sub-path expressions.
+        PathPatternExpr clonedPathPatternExpr =
+                new PathPatternExpr(clonedVertexExpressions, clonedEdgeExpressions, clonedVariableExpr);
+        for (LetClause letClause : pathPatternExpr.getReboundSubPathList()) {
+            clonedPathPatternExpr.getReboundSubPathList().add(this.visit(letClause, arg));
+        }
+        return clonedPathPatternExpr;
+    }
+
+    @Override
+    public EdgePatternExpr visit(EdgePatternExpr edgePatternExpr, Void arg) throws CompilationException {
+        // 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));
+        }
+        VariableExpr clonedVariableExpr = null;
+        if (edgePatternExpr.getEdgeDescriptor().getVariableExpr() != null) {
+            clonedVariableExpr = this.visit(edgePatternExpr.getEdgeDescriptor().getVariableExpr(), arg);
+        }
+
+        // Generate a cloned edge.
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        Set<ElementLabel> clonedEdgeDescriptorLabels = new HashSet<>(edgeDescriptor.getEdgeLabels());
+        EdgeDescriptor clonedDescriptor = new EdgeDescriptor(edgeDescriptor.getEdgeDirection(),
+                edgeDescriptor.getPatternType(), clonedEdgeDescriptorLabels, clonedVariableExpr,
+                edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops());
+        EdgePatternExpr clonedEdge = new EdgePatternExpr(clonedLeftVertex, clonedRightVertex, clonedDescriptor);
+        clonedEdge.replaceInternalVertices(clonedInternalVertices);
+        return clonedEdge;
+    }
+
+    @Override
+    public VertexPatternExpr visit(VertexPatternExpr vertexPatternExpr, Void arg) throws CompilationException {
+        VariableExpr clonedVariableExpr = null;
+        if (vertexPatternExpr.getVariableExpr() != null) {
+            clonedVariableExpr = this.visit(vertexPatternExpr.getVariableExpr(), arg);
+        }
+        Set<ElementLabel> clonedVertexLabels = new HashSet<>(vertexPatternExpr.getLabels());
+        return new VertexPatternExpr(clonedVariableExpr, clonedVertexLabels);
+    }
+
+    // We do not touch our GRAPH-CONSTRUCTOR here.
+    @Override
+    public ILangExpression visit(GraphConstructor gc, Void arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public ILangExpression visit(GraphConstructor.VertexConstructor vc, Void arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public ILangExpression visit(GraphConstructor.EdgeConstructor ec, Void arg) throws CompilationException {
+        return null;
+    }
+
+    // We can safely ignore the statements below, we will not encounter them in queries.
+    @Override
+    public ILangExpression visit(DeclareGraphStatement dgs, Void arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public ILangExpression visit(CreateGraphStatement cgs, Void arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public ILangExpression visit(GraphElementDeclaration gel, Void arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public ILangExpression visit(GraphDropStatement gds, Void arg) throws CompilationException {
+        return null;
+    }
+
+}
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/rewrites/visitor/GraphixFunctionCallVisitor.java
index 488acf6..399cb43 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/rewrites/visitor/GraphixFunctionCallVisitor.java
@@ -18,14 +18,13 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
-import java.util.List;
 import java.util.Map;
 
 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.FunctionRewriteMap;
 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;
@@ -33,17 +32,18 @@
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.statement.FunctionDecl;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
- * Replace all Graphix-specific function calls with either a SQL++ AST or a Graphix AST (that will later be lowered).
+ * Replace all Graphix-specific function calls with a SQL++ expression.
  *
  * @see GraphixFunctionIdentifiers
  * @see GraphixFunctionResolver
- * @see FunctionRewriteMap
+ * @see GraphixFunctionMap
  */
-public class GraphixFunctionCallVisitor extends AbstractGraphixQueryVisitor {
+public class GraphixFunctionCallVisitor extends AbstractSqlppSimpleExpressionVisitor {
     private final GraphixRewritingContext graphixRewritingContext;
     private final GraphixFunctionResolver graphixFunctionResolver;
 
@@ -59,17 +59,16 @@
     @Override
     public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
         // Determine which Graphix function we need to resolve.
-        FunctionSignature oldFunctionSignature = callExpr.getFunctionSignature();
-        FunctionSignature functionSignature = graphixFunctionResolver.resolve(oldFunctionSignature);
-        if (functionSignature == null) {
+        FunctionSignature functionSignature = graphixFunctionResolver.resolve(callExpr, true);
+        if (functionSignature == null || !functionSignature.getDataverseName().getCanonicalForm()
+                .equals(GraphixFunctionIdentifiers.GRAPHIX_DV.getCanonicalForm())) {
             return super.visit(callExpr, arg);
         }
 
         // Condition on our function ID, and perform the rewrite.
         FunctionIdentifier functionIdentifier = functionSignature.createFunctionIdentifier();
-        IFunctionRewrite functionRewrite = FunctionRewriteMap.getFunctionRewrite(functionIdentifier);
-        List<Expression> argList = callExpr.getExprList();
-        Expression rewrittenCallArgExpr = functionRewrite.apply(graphixRewritingContext, argList);
+        IFunctionRewrite functionRewrite = GraphixFunctionMap.getFunctionRewrite(functionIdentifier);
+        Expression rewrittenCallArgExpr = functionRewrite.apply(graphixRewritingContext, callExpr);
         if (rewrittenCallArgExpr == null) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, callExpr.getSourceLocation(),
                     "Function " + functionIdentifier.getName() + " not implemented!");
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
index d165bb8..0b55145 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
@@ -18,237 +18,298 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
 
 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.provider.GraphixCompilationProvider;
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
-import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
-import org.apache.asterix.graphix.lang.rewrites.assembly.IExprAssembly;
-import org.apache.asterix.graphix.lang.rewrites.common.EdgeDependencyGraph;
 import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
-import org.apache.asterix.graphix.lang.rewrites.lower.GraphixLowerSupplier;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierContext;
-import org.apache.asterix.graphix.lang.rewrites.lower.LowerSupplierNode;
-import org.apache.asterix.graphix.lang.rewrites.lower.assembly.ExpandEdgeLowerAssembly;
-import org.apache.asterix.graphix.lang.rewrites.visitor.ElementAnalysisVisitor.FromGraphClauseContext;
-import org.apache.asterix.lang.common.base.AbstractClause;
-import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.graphix.lang.rewrites.lower.EnvironmentActionFactory;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringAliasLookupTable;
+import org.apache.asterix.graphix.lang.rewrites.lower.LoweringEnvironment;
+import org.apache.asterix.graphix.lang.rewrites.visitor.ElementBodyAnalysisVisitor.ElementBodyAnalysisContext;
+import org.apache.asterix.graphix.lang.rewrites.visitor.StructureAnalysisVisitor.StructureContext;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.clause.LetClause;
-import org.apache.asterix.lang.common.expression.FieldAccessor;
-import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
-import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
-import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.JoinClause;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
-import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
-import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
-import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
-import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
-import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 
 /**
- * Rewrite a graph AST to utilize non-graph AST nodes (i.e. replace the GRAPH-SELECT-BLOCK with a SELECT-BLOCK).
- *
- * @see org.apache.asterix.graphix.lang.rewrites.lower.GraphixLowerSupplier
+ * Rewrite a graph AST to utilize non-graph AST nodes (i.e. replace GRAPH-SELECT-BLOCKs with a SELECT-BLOCK).
  */
 public class GraphixLoweringVisitor extends AbstractGraphixQueryVisitor {
-    private final Map<FromGraphClause, FromGraphClauseContext> fromGraphClauseContextMap;
-    private final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
+    private final Map<FromGraphClause, StructureContext> fromGraphClauseContextMap;
+    private final ElementLookupTable elementLookupTable;
     private final GraphixRewritingContext graphixRewritingContext;
+    private SelectExpression topLevelSelectExpression;
 
-    // In addition to the parent GRAPH-SELECT-BLOCK, we must keep track of the parent SELECT-EXPR.
-    private SelectExpression selectExpression;
+    // Our stack corresponds to which GRAPH-SELECT-BLOCK we are currently working with.
+    private final Map<GraphElementIdentifier, ElementBodyAnalysisContext> analysisContextMap;
+    private final Deque<LoweringEnvironment> environmentStack;
+    private final LoweringAliasLookupTable aliasLookupTable;
+    private final EnvironmentActionFactory environmentActionFactory;
 
-    public GraphixLoweringVisitor(Map<FromGraphClause, FromGraphClauseContext> fromGraphClauseContextMap,
-            ElementLookupTable<GraphElementIdentifier> elementLookupTable,
-            GraphixRewritingContext graphixRewritingContext) {
-        this.fromGraphClauseContextMap = fromGraphClauseContextMap;
-        this.elementLookupTable = elementLookupTable;
-        this.graphixRewritingContext = graphixRewritingContext;
+    public GraphixLoweringVisitor(GraphixRewritingContext graphixRewritingContext,
+            ElementLookupTable elementLookupTable, Map<FromGraphClause, StructureContext> fromGraphClauseContextMap) {
+        this.fromGraphClauseContextMap = Objects.requireNonNull(fromGraphClauseContextMap);
+        this.elementLookupTable = Objects.requireNonNull(elementLookupTable);
+        this.graphixRewritingContext = Objects.requireNonNull(graphixRewritingContext);
+        this.aliasLookupTable = new LoweringAliasLookupTable();
+        this.environmentStack = new ArrayDeque<>();
+
+        // All actions on our environment are supplied by the factory below.
+        Map<GraphElementIdentifier, ElementBodyAnalysisContext> bodyAnalysisContextMap = new HashMap<>();
+        this.environmentActionFactory = new EnvironmentActionFactory(bodyAnalysisContextMap, elementLookupTable,
+                aliasLookupTable, graphixRewritingContext);
+        this.analysisContextMap = bodyAnalysisContextMap;
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        if (!selectExpression.isSubquery() || topLevelSelectExpression == null) {
+            topLevelSelectExpression = selectExpression;
+        }
+        return super.visit(selectExpression, arg);
     }
 
     @Override
     public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
-        boolean hadFromGraphClause = false;
+        SelectExpression selectExpression = (SelectExpression) arg;
         if (graphSelectBlock.hasFromGraphClause()) {
-            // We will remove the FROM-GRAPH node and replace this with a FROM node on the child visit.
-            selectExpression = (SelectExpression) arg;
-            hadFromGraphClause = true;
-            graphSelectBlock.getFromGraphClause().accept(this, graphSelectBlock);
-        }
-        if (graphSelectBlock.hasLetWhereClauses()) {
-            for (AbstractClause clause : graphSelectBlock.getLetWhereList()) {
-                clause.accept(this, arg);
-            }
-        }
-        if (graphSelectBlock.hasGroupbyClause()) {
-            graphSelectBlock.getGroupbyClause().accept(this, arg);
-        }
-        if (graphSelectBlock.hasLetHavingClausesAfterGroupby()) {
-            for (AbstractClause clause : graphSelectBlock.getLetHavingListAfterGroupby()) {
-                clause.accept(this, arg);
-            }
-        }
-        if (hadFromGraphClause && graphSelectBlock.getSelectClause().selectRegular()) {
-            // A special case: if we have SELECT *, modify this to VAR.*.
-            if (graphSelectBlock.getSelectClause().getSelectRegular().getProjections().stream()
-                    .allMatch(p -> p.getKind() == Projection.Kind.STAR)) {
-                FromTerm fromTerm = graphSelectBlock.getFromClause().getFromTerms().get(0);
-                Projection projection = new Projection(Projection.Kind.VAR_STAR, fromTerm.getLeftVariable(), null);
-                SelectRegular selectRegular = new SelectRegular(List.of(projection));
-                graphSelectBlock.getSelectClause().setSelectRegular(selectRegular);
-            }
-        }
-        graphSelectBlock.getSelectClause().accept(this, arg);
-        return null;
-    }
+            FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
 
-    @Override
-    public Expression visit(LetClause letClause, ILangExpression arg) throws CompilationException {
-        // We also need to visit the binding variable here (in case we need to remove the GRAPH-VARIABLE-EXPR).
-        letClause.setVarExpr((VariableExpr) letClause.getVarExpr().accept(this, arg));
-        return super.visit(letClause, arg);
+            // Initialize a new lowering environment.
+            GraphIdentifier graphIdentifier = fromGraphClauseContextMap.get(fromGraphClause).getGraphIdentifier();
+            LoweringEnvironment newEnvironment = new LoweringEnvironment(graphSelectBlock, graphixRewritingContext);
+            environmentActionFactory.reset(graphIdentifier);
+
+            // We will remove the FROM-GRAPH node and replace this with a FROM node on the child visit.
+            environmentStack.addLast(newEnvironment);
+            super.visit(graphSelectBlock, graphSelectBlock);
+            environmentStack.removeLast();
+
+            // See if there are Graphix functions declared anywhere in our query.
+            Set<FunctionIdentifier> graphixFunctionSet = new HashSet<>();
+            topLevelSelectExpression.accept(new AbstractGraphixQueryVisitor() {
+                @Override
+                public Expression visit(CallExpr callExpr, ILangExpression arg) throws CompilationException {
+                    FunctionSignature functionSignature = callExpr.getFunctionSignature();
+                    if (functionSignature.getDataverseName().equals(GraphixFunctionIdentifiers.GRAPHIX_DV)) {
+                        graphixFunctionSet.add(functionSignature.createFunctionIdentifier());
+                    }
+                    return super.visit(callExpr, arg);
+                }
+            }, null);
+
+            // If so, then we need to perform a pass for schema enrichment.
+            if (!graphixFunctionSet.isEmpty()) {
+                SchemaEnrichmentVisitor schemaEnrichmentVisitor = new SchemaEnrichmentVisitor(elementLookupTable,
+                        graphIdentifier, graphSelectBlock, graphixFunctionSet);
+                selectExpression.accept(schemaEnrichmentVisitor, null);
+                if (selectExpression.hasOrderby()) {
+                    selectExpression.getOrderbyClause().accept(schemaEnrichmentVisitor, null);
+                }
+                if (selectExpression.hasLimit()) {
+                    selectExpression.getLimitClause().accept(schemaEnrichmentVisitor, null);
+                }
+            }
+
+        } else {
+            super.visit(graphSelectBlock, arg);
+        }
+        return null;
     }
 
     @Override
     public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
-        GraphSelectBlock parentGraphSelect = (GraphSelectBlock) arg;
-
-        // Build the context for our lowering strategy.
-        FromGraphClauseContext fromGraphClauseContext = fromGraphClauseContextMap.get(fromGraphClause);
-        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
-        DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
-                ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
-        String graphName = (fromGraphClause.getGraphName() != null) ? fromGraphClause.getGraphName().getValue()
-                : fromGraphClause.getGraphConstructor().getInstanceID();
-        List<PathPatternExpr> pathPatternExprList = fromGraphClause.getMatchClauses().stream()
-                .map(MatchClause::getPathExpressions).flatMap(Collection::stream).collect(Collectors.toList());
-        GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName);
-        LowerSupplierContext lowerSupplierContext = new LowerSupplierContext(graphixRewritingContext, graphIdentifier,
-                fromGraphClauseContext.getDanglingVertices(), fromGraphClauseContext.getOptionalVariables(),
-                pathPatternExprList, elementLookupTable);
-
-        // Determine our lowering strategy. By default, we will fully expand any variable-length edges.
-        String strategyMetadataKeyName = GraphixCompilationProvider.EDGE_STRATEGY_METADATA_CONFIG;
-        Function<EdgePatternExpr, IExprAssembly<LowerSupplierNode>> edgeHandlingStrategy;
-        if (metadataProvider.getConfig().containsKey(strategyMetadataKeyName)) {
-            String strategyProperty = metadataProvider.getProperty(strategyMetadataKeyName);
-            if (strategyProperty.equalsIgnoreCase(ExpandEdgeLowerAssembly.METADATA_CONFIG_NAME)) {
-                edgeHandlingStrategy = e -> new ExpandEdgeLowerAssembly(e, lowerSupplierContext);
-
-            } else {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                        "Unknown edge handling strategy specified: " + strategyProperty);
-            }
-
-        } else {
-            edgeHandlingStrategy = e -> new ExpandEdgeLowerAssembly(e, lowerSupplierContext);
+        // Perform an analysis pass over each element body. We need to determine what we can and can't inline.
+        for (GraphElementDeclaration graphElementDeclaration : elementLookupTable) {
+            ElementBodyAnalysisVisitor elementBodyAnalysisVisitor = new ElementBodyAnalysisVisitor();
+            GraphElementIdentifier elementIdentifier = graphElementDeclaration.getIdentifier();
+            graphElementDeclaration.getNormalizedBody().accept(elementBodyAnalysisVisitor, null);
+            analysisContextMap.put(elementIdentifier, elementBodyAnalysisVisitor.getElementBodyAnalysisContext());
         }
+        LoweringEnvironment workingEnvironment = environmentStack.getLast();
 
-        // Create a DAG of lowering nodes. We are interested in the tail of the generated DAG.
-        List<LowerSupplierNode> lowerSupplierNodeList = new ArrayList<>();
-        EdgeDependencyGraph edgeDependencyGraph = fromGraphClauseContext.getEdgeDependencyGraph();
-        new GraphixLowerSupplier(lowerSupplierContext, edgeDependencyGraph, edgeHandlingStrategy)
-                .invoke(lowerSupplierNodeList::add);
-        LowerSupplierNode tailLowerNode = lowerSupplierNodeList.get(lowerSupplierNodeList.size() - 1);
-
-        // Build our FROM-TERM, which will reference the tail lower node.
-        VariableExpr qualifyingVariable = tailLowerNode.getBindingVar();
-        FromTerm workingFromTerm = new FromTerm(qualifyingVariable, new VariableExpr(qualifyingVariable.getVar()), null,
-                fromGraphClause.getCorrelateClauses());
-        parentGraphSelect.setFromGraphClause(null);
-
-        // Introduce the variables from our assembly into our current SELECT-BLOCK.
-        List<AbstractClause> lowerNodeVariables = tailLowerNode.getProjectionList().stream().map(p -> {
-            FieldAccessor fieldAccessor = new FieldAccessor(qualifyingVariable, new Identifier(p.getName()));
-            String identifierName = SqlppVariableUtil.toInternalVariableName(p.getName());
-            VariableExpr variableExpr = new VariableExpr(new VarIdentifier(identifierName));
-            return new LetClause(variableExpr, fieldAccessor);
-        }).collect(Collectors.toList());
-        List<AbstractClause> newLetWhereList = new ArrayList<>();
-        newLetWhereList.addAll(lowerNodeVariables);
-        newLetWhereList.addAll(parentGraphSelect.getLetWhereList());
-        parentGraphSelect.getLetWhereList().clear();
-        parentGraphSelect.getLetWhereList().addAll(newLetWhereList);
-
-        // Qualify the graph element variables in our correlate clauses.
-        Supplier<AbstractGraphixQueryVisitor> qualifyingVisitorSupplier = () -> new AbstractGraphixQueryVisitor() {
-            private boolean isAtTopLevel = true;
-
-            @Override
-            public Expression visit(SelectExpression selectExpression, ILangExpression arg)
-                    throws CompilationException {
-                // Introduce our variables into the SELECT-EXPR inside a correlated clause.
-                List<LetClause> innerLowerNodeVariables = new ArrayList<>();
-                for (AbstractClause abstractClause : lowerNodeVariables) {
-                    innerLowerNodeVariables.add((LetClause) SqlppRewriteUtil.deepCopy(abstractClause));
-                }
-                List<LetClause> newLetClauseList = new ArrayList<>();
-                newLetClauseList.addAll(innerLowerNodeVariables);
-                newLetClauseList.addAll(selectExpression.getLetList());
-                selectExpression.getLetList().clear();
-                selectExpression.getLetList().addAll(newLetClauseList);
-
-                // Indicate that we should not qualify any of the variables in this SELECT.
-                boolean wasAtTopLevel = isAtTopLevel;
-                isAtTopLevel = false;
-                Expression resultOfVisit = super.visit(selectExpression, arg);
-                isAtTopLevel = wasAtTopLevel;
-                return resultOfVisit;
+        // Lower our MATCH-CLAUSEs. We should be working with canonical-ized patterns.
+        boolean wasInitialEdgeOrderingEncountered = false;
+        StructureContext structureContext = fromGraphClauseContextMap.get(fromGraphClause);
+        Deque<List<VertexPatternExpr>> danglingVertexQueue = structureContext.getDanglingVertexQueue();
+        Deque<List<PathPatternExpr>> pathPatternQueue = structureContext.getPathPatternQueue();
+        for (Iterable<EdgePatternExpr> edgeOrdering : structureContext.getEdgeDependencyGraph()) {
+            if (wasInitialEdgeOrderingEncountered) {
+                workingEnvironment.beginLeftMatch();
             }
-
-            @Override
-            public Expression visit(VariableExpr variableExpr, ILangExpression arg) throws CompilationException {
-                String variableName = SqlppVariableUtil.toUserDefinedName(variableExpr.getVar().getValue());
-                if (isAtTopLevel
-                        && tailLowerNode.getProjectionList().stream().anyMatch(p -> p.getName().equals(variableName))) {
-                    return new FieldAccessor(qualifyingVariable, new Identifier(variableName));
-                }
-                return super.visit(variableExpr, arg);
+            for (EdgePatternExpr edgePatternExpr : edgeOrdering) {
+                edgePatternExpr.accept(this, fromGraphClause);
             }
-        };
-        for (AbstractBinaryCorrelateClause correlateClause : workingFromTerm.getCorrelateClauses()) {
-            AbstractGraphixQueryVisitor variableVisitor = qualifyingVisitorSupplier.get();
-            if (correlateClause.getClauseType() == Clause.ClauseType.JOIN_CLAUSE) {
-                // The expression bound to a JOIN clause cannot see the graph element variables of this FROM-GRAPH.
-                JoinClause joinClause = (JoinClause) correlateClause;
-                joinClause.setConditionExpression(joinClause.getConditionExpression().accept(variableVisitor, null));
-
-            } else if (correlateClause.getClauseType() == Clause.ClauseType.UNNEST_CLAUSE) {
-                UnnestClause unnestClause = (UnnestClause) correlateClause;
-                unnestClause.setRightExpression(unnestClause.getRightExpression().accept(variableVisitor, null));
-
-            } else { // (correlateClause.getClauseType() == Clause.ClauseType.NEST_CLAUSE) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, correlateClause.getSourceLocation(),
-                        "Nest clause has not been implemented.");
+            for (VertexPatternExpr danglingVertexExpr : danglingVertexQueue.removeFirst()) {
+                workingEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(danglingVertexExpr));
+            }
+            if (wasInitialEdgeOrderingEncountered) {
+                workingEnvironment.endLeftMatch(graphixRewritingContext.getWarningCollector());
+            }
+            for (PathPatternExpr pathPatternExpr : pathPatternQueue.removeFirst()) {
+                workingEnvironment.acceptAction(environmentActionFactory.buildPathPatternAction(pathPatternExpr));
+            }
+            wasInitialEdgeOrderingEncountered = true;
+        }
+        if (!structureContext.getEdgeDependencyGraph().iterator().hasNext()) {
+            for (VertexPatternExpr danglingVertexExpr : danglingVertexQueue.removeFirst()) {
+                workingEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(danglingVertexExpr));
+            }
+            for (PathPatternExpr pathPatternExpr : pathPatternQueue.removeFirst()) {
+                workingEnvironment.acceptAction(environmentActionFactory.buildPathPatternAction(pathPatternExpr));
             }
         }
+        workingEnvironment.acceptAction(environmentActionFactory.buildIsomorphismAction(fromGraphClause));
 
-        // Finalize our lowering: replace our FROM-GRAPH-CLAUSE with a FROM-CLAUSE and add our LET-CLAUSE list.
-        parentGraphSelect.setFromClause(new FromClause(Collections.singletonList(workingFromTerm)));
-        lowerSupplierNodeList.forEach(g -> selectExpression.getLetList().add(g.buildLetClause()));
+        // Finalize our lowering by removing this FROM-GRAPH-CLAUSE from our parent GRAPH-SELECT-BLOCK.
+        workingEnvironment.finalizeLowering(fromGraphClause, graphixRewritingContext.getWarningCollector());
+
+        // Add our correlate clauses, if any, to our tail FROM-TERM.
+        if (!fromGraphClause.getCorrelateClauses().isEmpty()) {
+            GraphSelectBlock graphSelectBlock = (GraphSelectBlock) arg;
+            List<FromTerm> fromTerms = graphSelectBlock.getFromClause().getFromTerms();
+            FromTerm tailFromTerm = fromTerms.get(fromTerms.size() - 1);
+            tailFromTerm.getCorrelateClauses().addAll(fromGraphClause.getCorrelateClauses());
+        }
         return null;
     }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        LoweringEnvironment lowerEnvironment = environmentStack.getLast();
+
+        // We should only be working with one identifier (given that we only have one label).
+        GraphIdentifier graphIdentifier = fromGraphClauseContextMap.get((FromGraphClause) arg).getGraphIdentifier();
+        List<GraphElementIdentifier> edgeElementIDs = edgeDescriptor.generateIdentifiers(graphIdentifier);
+        if (edgeElementIDs.size() != 1) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, "Found non-canonical edge pattern!");
+        }
+        GraphElementIdentifier edgeIdentifier = edgeElementIDs.get(0);
+        ElementBodyAnalysisContext edgeBodyAnalysisContext = analysisContextMap.get(edgeIdentifier);
+        DataverseName edgeDataverseName = edgeBodyAnalysisContext.getDataverseName();
+        String edgeDatasetName = edgeBodyAnalysisContext.getDatasetName();
+        boolean isEdgeInline = edgeBodyAnalysisContext.isExpressionInline();
+
+        // Determine our source and destination vertices.
+        VertexPatternExpr sourceVertex, destVertex;
+        if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+            sourceVertex = edgePatternExpr.getLeftVertex();
+            destVertex = edgePatternExpr.getRightVertex();
+
+        } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
+            sourceVertex = edgePatternExpr.getRightVertex();
+            destVertex = edgePatternExpr.getLeftVertex();
+        }
+
+        // Collect information about our source -> edge JOIN.
+        GraphElementIdentifier sourceIdentifier = sourceVertex.generateIdentifiers(graphIdentifier).get(0);
+        ElementBodyAnalysisContext sourceBodyAnalysisContext = analysisContextMap.get(sourceIdentifier);
+        VarIdentifier sourceVertexVariable = sourceVertex.getVariableExpr().getVar();
+        List<List<String>> sourceVertexKey = elementLookupTable.getVertexKey(sourceIdentifier);
+        List<List<String>> sourceEdgeKey = elementLookupTable.getEdgeSourceKey(edgeIdentifier);
+        Function<GraphElementIdentifier, List<List<String>>> sourceKey = elementLookupTable::getEdgeSourceKey;
+        boolean isSourceInline = sourceBodyAnalysisContext.isExpressionInline();
+        boolean isSourceIntroduced = aliasLookupTable.getIterationAlias(sourceVertexVariable) != null;
+        boolean isSourceFolded = isSourceInline && sourceBodyAnalysisContext.getDatasetName().equals(edgeDatasetName)
+                && sourceBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
+                && sourceVertexKey.equals(sourceEdgeKey);
+
+        // ...and our dest -> edge JOIN.
+        GraphElementIdentifier destIdentifier = destVertex.generateIdentifiers(graphIdentifier).get(0);
+        ElementBodyAnalysisContext destBodyAnalysisContext = analysisContextMap.get(destIdentifier);
+        VarIdentifier destVertexVariable = destVertex.getVariableExpr().getVar();
+        List<List<String>> destVertexKey = elementLookupTable.getVertexKey(destIdentifier);
+        List<List<String>> destEdgeKey = elementLookupTable.getEdgeDestKey(edgeIdentifier);
+        Function<GraphElementIdentifier, List<List<String>>> destKey = elementLookupTable::getEdgeDestKey;
+        boolean isDestInline = destBodyAnalysisContext.isExpressionInline();
+        boolean isDestIntroduced = aliasLookupTable.getIterationAlias(destVertexVariable) != null;
+        boolean isDestFolded = isDestInline && destBodyAnalysisContext.getDatasetName().equals(edgeDatasetName)
+                && destBodyAnalysisContext.getDataverseName().equals(edgeDataverseName)
+                && destVertexKey.equals(destEdgeKey);
+
+        // Determine our strategy for lowering our edge.
+        if (isEdgeInline && isSourceFolded && !isDestIntroduced) {
+            if (!isSourceIntroduced) {
+                lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(sourceVertex));
+            }
+            lowerEnvironment
+                    .acceptAction(environmentActionFactory.buildFoldedEdgeAction(sourceVertex, edgePatternExpr));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isEdgeInline && isDestFolded && !isSourceIntroduced) {
+            if (!isDestIntroduced) {
+                lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(destVertex));
+            }
+            lowerEnvironment.acceptAction(environmentActionFactory.buildFoldedEdgeAction(destVertex, edgePatternExpr));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey));
+
+        } else if (isSourceIntroduced && isDestIntroduced) {
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildRawJoinVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isSourceIntroduced) { // !isDestIntroduced
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildNonFoldedEdgeAction(sourceVertex, edgePatternExpr, sourceKey));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildBoundVertexAction(destVertex, edgePatternExpr, destKey));
+
+        } else if (isDestIntroduced) { // !isSourceIntroduced
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildNonFoldedEdgeAction(destVertex, edgePatternExpr, destKey));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildBoundVertexAction(sourceVertex, edgePatternExpr, sourceKey));
+
+        } else { // !isSourceIntroduced && !isDestIntroduced
+            // When nothing is introduced, start off from LEFT to RIGHT instead of considering our source and dest.
+            VertexPatternExpr leftVertex = edgePatternExpr.getLeftVertex();
+            VertexPatternExpr rightVertex = edgePatternExpr.getRightVertex();
+            Function<GraphElementIdentifier, List<List<String>>> leftKey;
+            Function<GraphElementIdentifier, List<List<String>>> rightKey;
+            if (edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.LEFT_TO_RIGHT) {
+                leftKey = sourceKey;
+                rightKey = destKey;
+
+            } else { // edgeDescriptor.getEdgeDirection() == EdgeDescriptor.EdgeDirection.RIGHT_TO_LEFT
+                leftKey = destKey;
+                rightKey = sourceKey;
+            }
+            lowerEnvironment.acceptAction(environmentActionFactory.buildDanglingVertexAction(leftVertex));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildNonFoldedEdgeAction(leftVertex, edgePatternExpr, leftKey));
+            lowerEnvironment.acceptAction(
+                    environmentActionFactory.buildBoundVertexAction(rightVertex, edgePatternExpr, rightKey));
+        }
+        return edgePatternExpr;
+    }
 }
\ No newline at end of file
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java
new file mode 100644
index 0000000..fe4b8b8
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GroupByAggSugarVisitor.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.context.Scope;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGroupByAggregationSugarVisitor;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+
+/**
+ * An extension of {@link SqlppGroupByAggregationSugarVisitor} to properly handle {@link CorrLetClause} nodes.
+ */
+public class GroupByAggSugarVisitor extends SqlppGroupByAggregationSugarVisitor
+        implements ILetCorrelateClauseVisitor<Expression, ILangExpression> {
+    public GroupByAggSugarVisitor(LangRewritingContext context, Collection<VarIdentifier> externalVars) {
+        super(context, externalVars);
+    }
+
+    @Override
+    public Expression visit(CorrLetClause corrLetClause, ILangExpression arg) throws CompilationException {
+        // Do NOT extend the current scope.
+        corrLetClause.setRightExpression(visit(corrLetClause.getRightExpression(), corrLetClause));
+        VariableExpr varExpr = corrLetClause.getRightVariable();
+        Scope currentScope = scopeChecker.getCurrentScope();
+        if (currentScope.findLocalSymbol(varExpr.getVar().getValue()) != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, varExpr.getSourceLocation(),
+                    "Duplicate alias definitions: " + SqlppVariableUtil.toUserDefinedName(varExpr.getVar().getValue()));
+        }
+        currentScope.addNewVarSymbolToScope(varExpr.getVar(), Collections.emptySet());
+        return null;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java
index 723d4a5..9d5ece9 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/IGraphixLangVisitor.java
@@ -27,8 +27,9 @@
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 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.GraphElementDecl;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
 public interface IGraphixLangVisitor<R, T> extends ILangVisitor<R, T> {
@@ -38,9 +39,11 @@
 
     R visit(GraphConstructor.EdgeConstructor ee, T arg) throws CompilationException;
 
+    R visit(DeclareGraphStatement dgs, T arg) throws CompilationException;
+
     R visit(CreateGraphStatement cgs, T arg) throws CompilationException;
 
-    R visit(GraphElementDecl gel, T arg) throws CompilationException;
+    R visit(GraphElementDeclaration gel, T arg) throws CompilationException;
 
     R visit(GraphDropStatement gds, T arg) throws CompilationException;
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
similarity index 72%
copy from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
index 25efe98..dd8a89d 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ILetCorrelateClauseVisitor.java
@@ -16,14 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.asterix.graphix.lang.rewrites.assembly;
+package org.apache.asterix.graphix.lang.rewrites.visitor;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
-/**
- * @see ExprAssembler
- */
-@FunctionalInterface
-public interface IExprAssembly<T> {
-    T apply(T input) throws CompilationException;
+public interface ILetCorrelateClauseVisitor<R, T> extends ILangVisitor<R, T> {
+    R visit(CorrLetClause lcc, T arg) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java
new file mode 100644
index 0000000..7da0df5
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PopulateUnknownsVisitor.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Clause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.GenerateColumnNameVisitor;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.hyracks.algebricks.common.utils.Pair;
+
+/**
+ * A pre-Graphix transformation pass to populate a number of unknowns in our Graphix AST.
+ * a) Populate all unknown graph elements (vertices and edges).
+ * b) Populate all unknown column names in SELECT-CLAUSEs.
+ * c) Populate all unknown GROUP-BY keys.
+ * d) Fill in all GROUP-BY fields.
+ */
+public class PopulateUnknownsVisitor extends AbstractGraphixQueryVisitor {
+    private final GenerateColumnNameVisitor generateColumnNameVisitor;
+    private final Supplier<VarIdentifier> newVariableSupplier;
+
+    public PopulateUnknownsVisitor(GraphixRewritingContext graphixRewritingContext) {
+        generateColumnNameVisitor = new GenerateColumnNameVisitor(graphixRewritingContext);
+        newVariableSupplier = graphixRewritingContext::getNewGraphixVariable;
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        selectExpression.accept(generateColumnNameVisitor, arg);
+        return super.visit(selectExpression, arg);
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
+        super.visit(graphSelectBlock, arg);
+
+        if (graphSelectBlock.hasGroupbyClause()) {
+            // Collect all variables that should belong in the GROUP-BY field list.
+            List<VarIdentifier> userLiveVariables = new ArrayList<>();
+            for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
+                for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                    if (pathExpression.getVariableExpr() != null) {
+                        userLiveVariables.add(pathExpression.getVariableExpr().getVar());
+                    }
+                    for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+                        VarIdentifier vertexVariable = vertexExpression.getVariableExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(vertexVariable)) {
+                            userLiveVariables.add(vertexVariable);
+                        }
+                    }
+                    for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                        VarIdentifier edgeVariable = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(edgeVariable)) {
+                            userLiveVariables.add(edgeVariable);
+                        }
+                    }
+                }
+            }
+            if (!graphSelectBlock.getFromGraphClause().getCorrelateClauses().isEmpty()) {
+                FromGraphClause fromGraphClause = graphSelectBlock.getFromGraphClause();
+                List<AbstractBinaryCorrelateClause> correlateClauses = fromGraphClause.getCorrelateClauses();
+                for (AbstractBinaryCorrelateClause correlateClause : correlateClauses) {
+                    VarIdentifier bindingVariable = correlateClause.getRightVariable().getVar();
+                    if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
+                        userLiveVariables.add(bindingVariable);
+                    }
+                }
+            }
+            if (graphSelectBlock.hasLetWhereClauses()) {
+                for (AbstractClause abstractClause : graphSelectBlock.getLetWhereList()) {
+                    if (abstractClause.getClauseType() == Clause.ClauseType.LET_CLAUSE) {
+                        LetClause letClause = (LetClause) abstractClause;
+                        VarIdentifier bindingVariable = letClause.getVarExpr().getVar();
+                        if (!GraphixRewritingContext.isGraphixVariable(bindingVariable)) {
+                            userLiveVariables.add(bindingVariable);
+                        }
+                    }
+                }
+            }
+
+            // Add the live variables to our GROUP-BY field list.
+            List<Pair<Expression, Identifier>> newGroupFieldList = new ArrayList<>();
+            for (VarIdentifier userLiveVariable : userLiveVariables) {
+                String variableName = SqlppVariableUtil.toUserDefinedName(userLiveVariable.getValue());
+                VariableExpr variableExpr = new VariableExpr(userLiveVariable);
+                newGroupFieldList.add(new Pair<>(variableExpr, new Identifier(variableName)));
+            }
+            graphSelectBlock.getGroupbyClause().setGroupFieldList(newGroupFieldList);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
+        if (vertexExpression.getVariableExpr() == null) {
+            vertexExpression.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
+        }
+        return super.visit(vertexExpression, arg);
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
+        if (edgeDescriptor.getVariableExpr() == null) {
+            edgeDescriptor.setVariableExpr(new VariableExpr(newVariableSupplier.get()));
+        }
+        return super.visit(edgeExpression, arg);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java
new file mode 100644
index 0000000..bf6cb97
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostCanonicalizationVisitor.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.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.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.LimitClause;
+import org.apache.asterix.lang.common.clause.OrderbyClause;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+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.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+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.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationRight;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+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.
+ */
+public class PostCanonicalizationVisitor extends AbstractGraphixQueryVisitor {
+    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) {
+        this.graphixRewritingContext = graphixRewritingContext;
+        this.selectBlockExpansionSource = selectBlockExpansionSource;
+        this.generatedSetOpInputs = generatedSetOpInputs;
+        this.userLiveVariables = userLiveVariables;
+        this.qualifyingVisitor = new QualifyingVisitor();
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        VariableExpr iterationVariableExpr = new VariableExpr(graphixRewritingContext.getNewGraphixVariable());
+
+        // 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);
+        FromClause fromClause = new FromClause(List.of(fromTerm));
+
+        // Qualify the SELECT-CLAUSE given to us by our caller.
+        qualifyingVisitor.qualifyingVar = iterationVariableExpr.getVar();
+        SelectClause selectClause = selectBlockExpansionSource.getSelectClause();
+        selectClause.accept(qualifyingVisitor, null);
+
+        // Modify our output modifiers (if any) to qualify them with our output variable.
+        OrderbyClause orderByClause = selectExpression.getOrderbyClause();
+        LimitClause limitClause = selectExpression.getLimitClause();
+        if (selectExpression.hasOrderby()) {
+            orderByClause.accept(qualifyingVisitor, null);
+        }
+        if (selectExpression.hasLimit()) {
+            limitClause.accept(qualifyingVisitor, null);
+        }
+
+        // Remove the output modifiers from our current SELECT-EXPR.
+        boolean isSubquery = selectExpression.isSubquery();
+        selectExpression.setLimitClause(null);
+        selectExpression.setOrderbyClause(null);
+        selectExpression.setSubquery(true);
+
+        // Modify our GROUP-BY (if any) to qualify them with our output variable.
+        GroupbyClause groupbyClause = selectBlockExpansionSource.getGroupbyClause();
+        List<AbstractClause> letHavingClausesAfterGby = selectBlockExpansionSource.getLetHavingListAfterGroupby();
+        if (selectBlockExpansionSource.hasGroupbyClause()) {
+            groupbyClause.accept(qualifyingVisitor, null);
+
+            // Ensure that any variables that may be used after the GROUP-BY don't see this qualifying variable.
+            List<Pair<Expression, Identifier>> newGroupFieldList = new ArrayList<>();
+            for (Pair<Expression, Identifier> expressionIdentifierPair : groupbyClause.getGroupFieldList()) {
+                Expression newExpression = expressionIdentifierPair.first.accept(qualifyingVisitor, null);
+                newGroupFieldList.add(new Pair<>(newExpression, expressionIdentifierPair.second));
+            }
+            groupbyClause.setGroupFieldList(newGroupFieldList);
+        }
+        if (selectBlockExpansionSource.hasLetHavingClausesAfterGroupby()) {
+            for (AbstractClause abstractClause : letHavingClausesAfterGby) {
+                abstractClause.accept(qualifyingVisitor, null);
+            }
+        }
+
+        // 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);
+        SetOperationInput setOperationInput = new SetOperationInput(selectBlock, null);
+        SelectSetOperation selectSetOperation = new SelectSetOperation(setOperationInput, null);
+        return new SelectExpression(null, selectSetOperation, orderByClause, limitClause, isSubquery);
+    }
+
+    @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)) {
+            leftInput.getSelectBlock().accept(this, arg);
+        }
+        for (SetOperationRight setOperationRight : selectSetOperation.getRightInputs()) {
+            SetOperationInput rightInput = setOperationRight.getSetOperationRightInput();
+            if (generatedSetOpInputs.contains(rightInput)) {
+                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()) {
+            selectBlock.setGroupbyClause(null);
+        }
+        if (selectBlock.hasLetHavingClausesAfterGroupby()) {
+            selectBlock.getLetHavingListAfterGroupby().clear();
+        }
+        selectBlock.getSelectClause().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    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));
+        }
+        selectClause.setSelectElement(null);
+        selectClause.setSelectRegular(new SelectRegular(newProjectionList));
+        return null;
+    }
+
+    private class QualifyingVisitor extends AbstractGraphixQueryVisitor {
+        private VarIdentifier qualifyingVar;
+
+        @Override
+        public Expression visit(VariableExpr variableExpr, ILangExpression arg) throws CompilationException {
+            if (userLiveVariables.contains(variableExpr.getVar())) {
+                VarIdentifier fieldAccessVar = SqlppVariableUtil.toUserDefinedVariableName(variableExpr.getVar());
+                return new FieldAccessor(new VariableExpr(qualifyingVar), fieldAccessVar);
+            }
+            return super.visit(variableExpr, arg);
+        }
+
+        @Override
+        public Expression visit(FieldAccessor fieldAccessor, ILangExpression arg) throws CompilationException {
+            Expression fieldAccessorExpr = fieldAccessor.getExpr();
+            if (fieldAccessorExpr.getKind() == Expression.Kind.FIELD_ACCESSOR_EXPRESSION) {
+                FieldAccessor innerFieldAccessExpr = (FieldAccessor) fieldAccessorExpr.accept(this, arg);
+                return new FieldAccessor(innerFieldAccessExpr, fieldAccessor.getIdent());
+
+            } 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());
+                }
+            }
+            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/rewrites/visitor/PostRewriteCheckVisitor.java
index c184cb5..a1ae83d 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/rewrites/visitor/PostRewriteCheckVisitor.java
@@ -28,8 +28,9 @@
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 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.GraphElementDecl;
+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;
@@ -61,12 +62,17 @@
     }
 
     @Override
+    public Expression visit(DeclareGraphStatement dgs, ILangExpression arg) throws CompilationException {
+        return throwException(dgs);
+    }
+
+    @Override
     public Expression visit(CreateGraphStatement cgs, ILangExpression arg) throws CompilationException {
         return throwException(cgs);
     }
 
     @Override
-    public Expression visit(GraphElementDecl gel, ILangExpression arg) throws CompilationException {
+    public Expression visit(GraphElementDeclaration gel, ILangExpression arg) throws CompilationException {
         return throwException(gel);
     }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java
new file mode 100644
index 0000000..eea6b98
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteVariableVisitor.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.context.Scope;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.rewrites.visitor.VariableCheckAndRewriteVisitor;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+
+/**
+ * An extension of {@link VariableCheckAndRewriteVisitor} to properly handle {@link CorrLetClause} nodes.
+ */
+public class PostRewriteVariableVisitor extends VariableCheckAndRewriteVisitor
+        implements ILetCorrelateClauseVisitor<Expression, ILangExpression> {
+    public PostRewriteVariableVisitor(LangRewritingContext context, MetadataProvider metadataProvider,
+            Collection<VarIdentifier> externalVars) {
+        super(context, metadataProvider, externalVars);
+    }
+
+    @Override
+    public Expression visit(CorrLetClause corrLetClause, ILangExpression arg) throws CompilationException {
+        // Do NOT extend the current scope.
+        corrLetClause.setRightExpression(visit(corrLetClause.getRightExpression(), corrLetClause));
+        VariableExpr varExpr = corrLetClause.getRightVariable();
+        Scope currentScope = scopeChecker.getCurrentScope();
+        if (currentScope.findLocalSymbol(varExpr.getVar().getValue()) != null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, varExpr.getSourceLocation(),
+                    "Duplicate alias definitions: " + SqlppVariableUtil.toUserDefinedName(varExpr.getVar().getValue()));
+        }
+        currentScope.addNewVarSymbolToScope(varExpr.getVar(), Collections.emptySet());
+        return null;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
index f2e2605..be82df7 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
@@ -18,6 +18,8 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
+import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
+
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -27,12 +29,13 @@
 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.extension.GraphixMetadataExtension;
 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.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.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.Graph;
@@ -40,7 +43,6 @@
 import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.metadata.declared.MetadataProvider;
@@ -54,9 +56,10 @@
  * 4. A vertex label exists in the context of the vertex's {@link FromGraphClause}.
  * 5. The minimum hops and maximum hops of a sub-path is not equal to zero.
  * 6. The maximum hops of a sub-path is greater than or equal to the minimum hops of the same sub-path.
- * 7. An anonymous graph passes the same validation that a named graph does.
+ * 7. An anonymous / declared graph passes the same validation that a named graph does.
  */
 public class PreRewriteCheckVisitor extends AbstractGraphixQueryVisitor {
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
     private final MetadataProvider metadataProvider;
 
     // Build new environments on each FROM-GRAPH-CLAUSE visit.
@@ -69,8 +72,9 @@
 
     private final Map<ILangExpression, PreRewriteCheckEnvironment> environmentMap = new HashMap<>();
 
-    public PreRewriteCheckVisitor(LangRewritingContext langRewritingContext) {
-        this.metadataProvider = langRewritingContext.getMetadataProvider();
+    public PreRewriteCheckVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
+        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
     }
 
     @Override
@@ -82,13 +86,13 @@
         // Perform the same validation we do for named graphs-- but don't build the schema object.
         for (GraphConstructor.VertexConstructor vertex : graphConstructor.getVertexElements()) {
             schemaBuilder.addVertex(vertex.getLabel(), vertex.getPrimaryKeyFields(), vertex.getDefinition());
-            if (schemaBuilder.getLastError() == Schema.Builder.Error.CONFLICTING_PRIMARY_KEY) {
+            if (schemaBuilder.getLastError() == Schema.Builder.Error.VERTEX_LABEL_CONFLICT) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertex.getSourceLocation(),
-                        "Conflicting primary keys for vertices with label " + vertex.getLabel());
+                        "Conflicting vertex label found: " + vertex.getLabel());
 
             } else if (schemaBuilder.getLastError() != Schema.Builder.Error.NO_ERROR) {
                 throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, vertex.getSourceLocation(),
-                        "Constructor vertex was not returned, but the error is not a conflicting primary key!");
+                        "Constructor vertex was not returned, but the error is not a conflicting vertex label!");
             }
         }
         for (GraphConstructor.EdgeConstructor edge : graphConstructor.getEdgeElements()) {
@@ -108,10 +112,9 @@
                             "Destination vertex " + edge.getDestinationLabel() + " not found in the edge "
                                     + edge.getEdgeLabel() + ".");
 
-                case CONFLICTING_SOURCE_KEY:
-                case CONFLICTING_DESTINATION_KEY:
+                case EDGE_LABEL_CONFLICT:
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
-                            "Conflicting edge with the same label found: " + edge.getEdgeLabel());
+                            "Conflicting edge label found: " + edge.getEdgeLabel());
 
                 default:
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, edge.getSourceLocation(),
@@ -126,49 +129,55 @@
         environmentMap.put(fromGraphClause, new PreRewriteCheckEnvironment());
 
         // Establish the vertex and edge labels associated with this FROM-GRAPH-CLAUSE.
-        if (fromGraphClause.getGraphConstructor() == null) {
+        GraphConstructor graphConstructor = fromGraphClause.getGraphConstructor();
+        if (graphConstructor == null) {
             DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
                     ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
             Identifier graphName = fromGraphClause.getGraphName();
 
-            // Fetch the graph from our metadata.
-            try {
-                Graph graphFromMetadata = GraphixMetadataExtension.getGraph(metadataProvider.getMetadataTxnContext(),
-                        dataverseName, graphName.getValue());
-                if (graphFromMetadata == null) {
+            // First, see if we can fetch the graph constructor from our declared graphs.
+            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
+            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
+            if (declaredGraph != null) {
+                graphConstructor = declaredGraph.getGraphConstructor();
+
+            } else {
+                // Otherwise, fetch the graph from our metadata.
+                try {
+                    Graph graphFromMetadata =
+                            getGraph(metadataProvider.getMetadataTxnContext(), dataverseName, graphName.getValue());
+                    if (graphFromMetadata == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                                "Graph " + graphName.getValue() + " does not exist.");
+
+                    } else {
+                        graphFromMetadata.getGraphSchema().getVertices().stream().map(Vertex::getLabel)
+                                .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
+                        graphFromMetadata.getGraphSchema().getEdges().forEach(e -> {
+                            environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
+                            environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
+                            environmentMap.get(fromGraphClause).edgeLabels.add(e.getLabel());
+                        });
+                    }
+
+                } catch (AlgebricksException e) {
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
                             "Graph " + graphName.getValue() + " does not exist.");
-
-                } else {
-                    graphFromMetadata.getGraphSchema().getVertices().stream().map(Vertex::getLabel)
-                            .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
-                    graphFromMetadata.getGraphSchema().getEdges().forEach(e -> {
-                        environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
-                        environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
-                        environmentMap.get(fromGraphClause).edgeLabels.add(e.getLabel());
-                    });
                 }
-
-            } catch (AlgebricksException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                        "Graph " + graphName.getValue() + " does not exist.");
             }
-
-        } else {
-            fromGraphClause.getGraphConstructor().getVertexElements().stream()
-                    .map(GraphConstructor.VertexConstructor::getLabel)
+        }
+        if (graphConstructor != null) {
+            graphConstructor.getVertexElements().stream().map(GraphConstructor.VertexConstructor::getLabel)
                     .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
-            fromGraphClause.getGraphConstructor().getEdgeElements().forEach(e -> {
+            graphConstructor.getEdgeElements().forEach(e -> {
                 environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
                 environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
                 environmentMap.get(fromGraphClause).edgeLabels.add(e.getEdgeLabel());
             });
+            graphConstructor.accept(this, arg);
         }
 
         // We need to pass our FROM-GRAPH-CLAUSE to our MATCH-CLAUSE.
-        if (fromGraphClause.getGraphConstructor() != null) {
-            fromGraphClause.getGraphConstructor().accept(this, arg);
-        }
         for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
             matchClause.accept(this, fromGraphClause);
         }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java
new file mode 100644
index 0000000..f91ebb7
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/SchemaEnrichmentVisitor.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isEdgeFunction;
+import static org.apache.asterix.graphix.function.GraphixFunctionIdentifiers.isVertexFunction;
+import static org.apache.asterix.graphix.function.GraphixFunctionMap.getFunctionPrepare;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.function.prepare.IFunctionPrepare;
+import org.apache.asterix.graphix.lang.clause.CorrLetClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+/**
+ * Perform a pass to enrich any {@link CorrLetClause} nodes with necessary schema information. Note that this
+ * process may overestimate the amount of enrichment actually required (to minimize this difference requires some form
+ * of equivalence classes @ the rewriter level).
+ */
+public class SchemaEnrichmentVisitor extends AbstractGraphixQueryVisitor {
+    private final ElementLookupTable elementLookupTable;
+    private final Set<FunctionIdentifier> functionIdentifiers;
+    private final Map<VarIdentifier, Expression> expressionMap;
+    private final GraphSelectBlock workingSelectBlock;
+    private final GraphIdentifier graphIdentifier;
+
+    public SchemaEnrichmentVisitor(ElementLookupTable elementLookupTable, GraphIdentifier graphIdentifier,
+            GraphSelectBlock workingSelectBlock, Set<FunctionIdentifier> functionIdentifiers) {
+        this.graphIdentifier = graphIdentifier;
+        this.elementLookupTable = elementLookupTable;
+        this.workingSelectBlock = workingSelectBlock;
+        this.functionIdentifiers = functionIdentifiers;
+        this.expressionMap = new HashMap<>();
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
+        // Visit our immediate MATCH AST nodes (do not visit lower levels).
+        for (MatchClause matchClause : graphSelectBlock.getFromGraphClause().getMatchClauses()) {
+            matchClause.accept(this, arg);
+        }
+
+        // We are going to enrich these LET-CORRELATE clauses w/ the necessary schema.
+        if (workingSelectBlock.equals(graphSelectBlock)) {
+            List<CorrLetClause> corrLetClauses = graphSelectBlock.getFromClause().getFromTerms().get(0)
+                    .getCorrelateClauses().stream().filter(c -> c instanceof CorrLetClause).map(c -> (CorrLetClause) c)
+                    .collect(Collectors.toList());
+            Collections.reverse(corrLetClauses);
+            for (FunctionIdentifier functionIdentifier : functionIdentifiers) {
+                IFunctionPrepare functionPrepare = getFunctionPrepare(functionIdentifier);
+                Set<VarIdentifier> visitedBindings = new HashSet<>();
+                for (CorrLetClause corrLetClause : corrLetClauses) {
+                    VarIdentifier rightVar = corrLetClause.getRightVariable().getVar();
+                    Expression graphExpr = expressionMap.getOrDefault(rightVar, null);
+                    boolean isVertex = isVertexFunction(functionIdentifier) && graphExpr instanceof VertexPatternExpr;
+                    boolean isEdge = isEdgeFunction(functionIdentifier) && graphExpr instanceof EdgePatternExpr;
+                    if (!visitedBindings.contains(rightVar) && (isEdge || isVertex)) {
+                        Expression outputExpr = functionPrepare.prepare(corrLetClause.getRightExpression(), graphExpr,
+                                graphIdentifier, elementLookupTable);
+                        corrLetClause.setRightExpression(outputExpr);
+                        visitedBindings.add(rightVar);
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
+        VariableExpr variableExpr = vertexPatternExpr.getVariableExpr();
+        if (variableExpr != null) {
+            expressionMap.put(variableExpr.getVar(), vertexPatternExpr);
+        }
+        return super.visit(vertexPatternExpr, arg);
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        VariableExpr variableExpr = edgeDescriptor.getVariableExpr();
+        if (variableExpr != null) {
+            expressionMap.put(variableExpr.getVar(), edgePatternExpr);
+        }
+        return super.visit(edgePatternExpr, arg);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
index 24b8625..2b01f9c 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
@@ -31,15 +31,16 @@
 import org.apache.asterix.graphix.lang.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.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.GraphElementDecl;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
 import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.expression.VariableExpr;
-import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
 import org.apache.asterix.lang.sqlpp.clause.Projection;
@@ -68,8 +69,8 @@
         implements IGraphixLangVisitor<Expression, ILangExpression> {
     private final Deque<Mutable<Boolean>> graphixVisitStack = new ArrayDeque<>();
 
-    public ScopingCheckVisitor(LangRewritingContext context) {
-        super(context);
+    public ScopingCheckVisitor(GraphixRewritingContext graphixRewritingContext) {
+        super(graphixRewritingContext);
 
         // We start with an element of false in our stack.
         graphixVisitStack.addLast(new MutableObject<>(false));
@@ -224,13 +225,20 @@
 
     // The following should not appear in queries.
     @Override
+    public Expression visit(DeclareGraphStatement declareGraphStatement, ILangExpression arg)
+            throws CompilationException {
+        return null;
+    }
+
+    @Override
     public Expression visit(CreateGraphStatement createGraphStatement, ILangExpression arg)
             throws CompilationException {
         return null;
     }
 
     @Override
-    public Expression visit(GraphElementDecl graphElementDecl, ILangExpression arg) throws CompilationException {
+    public Expression visit(GraphElementDeclaration graphElementDeclaration, ILangExpression arg)
+            throws CompilationException {
         return null;
     }
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementAnalysisVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java
similarity index 63%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementAnalysisVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java
index 1c3882c..36969e2 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementAnalysisVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureAnalysisVisitor.java
@@ -18,87 +18,109 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.clause.MatchClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 import org.apache.asterix.graphix.lang.optype.MatchType;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.rewrites.common.EdgeDependencyGraph;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.metadata.declared.MetadataProvider;
 
 /**
  * Perform an analysis pass of our AST to generate three items: a) a list of edge orderings, b) a list of dangling
- * vertices, and c) a list of optional variables (i.e. vertices) per FROM-GRAPH-CLAUSE.
+ * vertices, and c) a list of path patterns.
  * - To generate an {@link EdgeDependencyGraph}, we create an adjacency map of query edges whose adjacency between
  * other query edges is defined as sharing some vertex. The adjacency list associated with each query edge is ordered
  * by visitation, and will be used in {@link EdgeDependencyGraph#iterator()}- in short, this means that the order of
  * the patterns in query may affect the resulting lowered AST.
  * - For each LEFT-MATCH-CLAUSE, we build a new dependency subgraph. Every subsequent subgraph should possess a
  * leading edge that contains a vertex found in the previous subgraph (if it exists).
- * - To generate our list of optional vertices, we first collect all vertices found before the first LEFT-MATCH-CLAUSE.
- * For the set of vertices found after this LEFT-MATCH-CLAUSE, we take the difference of this set and the previous set.
  */
-public class ElementAnalysisVisitor extends AbstractGraphixQueryVisitor {
+public class StructureAnalysisVisitor extends AbstractGraphixQueryVisitor {
     private static class AnalysisEnvironment {
         // We must populate these maps (edge ID -> edge IDs).
-        private final List<Map<Identifier, List<Identifier>>> adjacencyMaps = new ArrayList<>();
-        private final Map<Identifier, EdgePatternExpr> edgePatternMap = new LinkedHashMap<>();
+        private final List<Map<Identifier, List<Identifier>>> adjacencyMaps;
+        private final Map<Identifier, EdgePatternExpr> edgePatternMap;
+        private Map<Identifier, List<Identifier>> workingAdjacencyMap;
 
         // Build a separate map to define dependencies (vertex ID -> edge IDs).
-        private final Map<Identifier, List<Identifier>> vertexEdgeMap = new LinkedHashMap<>();
-        private final List<VertexPatternExpr> danglingVertices = new ArrayList<>();
+        private final Map<Identifier, List<Identifier>> vertexEdgeMap;
+        private final Deque<List<VertexPatternExpr>> danglingVertices;
+        private final Deque<List<PathPatternExpr>> pathPatterns;
 
-        // To allow for LEFT-MATCH patterns, we must keep track of which vertices are not mandatory.
-        private final Set<VarIdentifier> visitedVertices = new HashSet<>();
-        private final Set<VarIdentifier> mandatoryVertices = new HashSet<>();
-        private final Set<VarIdentifier> optionalVertices = new HashSet<>();
-        private Map<Identifier, List<Identifier>> workingAdjacencyMap = new LinkedHashMap<>();
+        private AnalysisEnvironment() {
+            this.adjacencyMaps = new ArrayList<>();
+            this.edgePatternMap = new LinkedHashMap<>();
+            this.workingAdjacencyMap = new LinkedHashMap<>();
+
+            this.pathPatterns = new ArrayDeque<>();
+            this.danglingVertices = new ArrayDeque<>();
+            this.pathPatterns.addLast(new ArrayList<>());
+            this.danglingVertices.addLast(new ArrayList<>());
+            this.vertexEdgeMap = new LinkedHashMap<>();
+        }
     }
 
     // We will return instances of the following back to our caller.
-    public static class FromGraphClauseContext {
+    public static class StructureContext {
+        private final Deque<List<PathPatternExpr>> pathPatternQueue;
+        private final Deque<List<VertexPatternExpr>> danglingVertexQueue;
         private final EdgeDependencyGraph edgeDependencyGraph;
-        private final List<VertexPatternExpr> danglingVertices;
-        private final List<VarIdentifier> optionalVariables;
+        private final GraphIdentifier graphIdentifier;
 
-        private FromGraphClauseContext(EdgeDependencyGraph edgeDependencyGraph,
-                List<VertexPatternExpr> danglingVertices, List<VarIdentifier> optionalVariables) {
-            this.edgeDependencyGraph = edgeDependencyGraph;
-            this.danglingVertices = danglingVertices;
-            this.optionalVariables = optionalVariables;
+        private StructureContext(EdgeDependencyGraph edgeGraph, Deque<List<PathPatternExpr>> pathPatternQueue,
+                Deque<List<VertexPatternExpr>> danglingVertexQueue, GraphIdentifier graphIdentifier) {
+            this.edgeDependencyGraph = edgeGraph;
+            this.pathPatternQueue = pathPatternQueue;
+            this.danglingVertexQueue = danglingVertexQueue;
+            this.graphIdentifier = graphIdentifier;
         }
 
         public EdgeDependencyGraph getEdgeDependencyGraph() {
             return edgeDependencyGraph;
         }
 
-        public List<VertexPatternExpr> getDanglingVertices() {
-            return danglingVertices;
+        public Deque<List<VertexPatternExpr>> getDanglingVertexQueue() {
+            return danglingVertexQueue;
         }
 
-        public List<VarIdentifier> getOptionalVariables() {
-            return optionalVariables;
+        public Deque<List<PathPatternExpr>> getPathPatternQueue() {
+            return pathPatternQueue;
+        }
+
+        public GraphIdentifier getGraphIdentifier() {
+            return graphIdentifier;
         }
     }
 
     // We will build new environments on each visit of a FROM-GRAPH-CLAUSE.
-    private final Map<FromGraphClause, AnalysisEnvironment> analysisEnvironmentMap = new HashMap<>();
+    private final Map<FromGraphClause, AnalysisEnvironment> analysisEnvironmentMap;
+    private final GraphixRewritingContext graphixRewritingContext;
     private AnalysisEnvironment workingEnvironment;
 
+    public StructureAnalysisVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.analysisEnvironmentMap = new HashMap<>();
+        this.graphixRewritingContext = graphixRewritingContext;
+    }
+
     private void addEdgeDependency(VarIdentifier vertexID, VarIdentifier edgeID, List<Identifier> dependencyList) {
         if (workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
             dependencyList.addAll(workingEnvironment.vertexEdgeMap.get(vertexID));
@@ -116,6 +138,8 @@
         // Add to our a map a new analysis environment.
         workingEnvironment = new AnalysisEnvironment();
         analysisEnvironmentMap.put(fromGraphClause, workingEnvironment);
+
+        // Collect our structure context.
         return super.visit(fromGraphClause, arg);
     }
 
@@ -123,21 +147,15 @@
     public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
         // Reset to build a new graph.
         if (matchClause.getMatchType() == MatchType.LEFTOUTER) {
-            workingEnvironment.adjacencyMaps.add(workingEnvironment.workingAdjacencyMap);
-            workingEnvironment.workingAdjacencyMap = new LinkedHashMap<>();
+            workingEnvironment.danglingVertices.addLast(new ArrayList<>());
+            workingEnvironment.pathPatterns.addLast(new ArrayList<>());
             workingEnvironment.vertexEdgeMap.clear();
-        }
-        super.visit(matchClause, arg);
 
-        // Record which vertices are mandatory and which are optional.
-        if (matchClause.getMatchType() != MatchType.LEFTOUTER && workingEnvironment.optionalVertices.isEmpty()) {
-            workingEnvironment.mandatoryVertices.addAll(workingEnvironment.visitedVertices);
-
-        } else { // We have encountered a LEFT-OUTER-MATCH-CLAUSE.
-            workingEnvironment.visitedVertices.stream().filter(v -> !workingEnvironment.mandatoryVertices.contains(v))
-                    .forEach(workingEnvironment.optionalVertices::add);
+            // We should retain all adjacency information from the previous walks.
+            workingEnvironment.adjacencyMaps.add(workingEnvironment.workingAdjacencyMap);
+            workingEnvironment.workingAdjacencyMap = new LinkedHashMap<>(workingEnvironment.workingAdjacencyMap);
         }
-        return null;
+        return super.visit(matchClause, arg);
     }
 
     @Override
@@ -149,6 +167,7 @@
         for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
             vertexExpression.accept(this, arg);
         }
+        workingEnvironment.pathPatterns.getLast().add(pathExpression);
         return pathExpression;
     }
 
@@ -156,9 +175,8 @@
     public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
         VarIdentifier vertexID = vertexExpression.getVariableExpr().getVar();
         if (!workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
-            workingEnvironment.danglingVertices.add(vertexExpression);
+            workingEnvironment.danglingVertices.getLast().add(vertexExpression);
         }
-        workingEnvironment.visitedVertices.add(vertexID);
         return vertexExpression;
     }
 
@@ -182,7 +200,7 @@
         return edgeExpression;
     }
 
-    public Map<FromGraphClause, FromGraphClauseContext> getFromGraphClauseContextMap() {
+    public Map<FromGraphClause, StructureContext> getFromGraphClauseContextMap() {
         return analysisEnvironmentMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> {
             AnalysisEnvironment analysisEnvironment = e.getValue();
             if (!analysisEnvironment.workingAdjacencyMap.isEmpty()) {
@@ -192,12 +210,22 @@
                 analysisEnvironment.vertexEdgeMap.clear();
             }
 
+            // Build our graph identifier.
+            FromGraphClause fromGraphClause = e.getKey();
+            MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
+                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
+            String graphName = (fromGraphClause.getGraphName() != null) ? fromGraphClause.getGraphName().getValue()
+                    : fromGraphClause.getGraphConstructor().getInstanceID();
+            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName);
+
             // Construct our output.
-            EdgeDependencyGraph edgeDependencyGraph =
-                    new EdgeDependencyGraph(e.getValue().adjacencyMaps, e.getValue().edgePatternMap);
-            List<VertexPatternExpr> danglingVertices = e.getValue().danglingVertices;
-            List<VarIdentifier> optionalVariables = new ArrayList<>(e.getValue().optionalVertices);
-            return new FromGraphClauseContext(edgeDependencyGraph, danglingVertices, optionalVariables);
+            List<Map<Identifier, List<Identifier>>> adjacencyMaps = e.getValue().adjacencyMaps;
+            Map<Identifier, EdgePatternExpr> edgePatternMap = e.getValue().edgePatternMap;
+            EdgeDependencyGraph edgeDependencyGraph = new EdgeDependencyGraph(adjacencyMaps, edgePatternMap);
+            Deque<List<VertexPatternExpr>> danglingVertices = e.getValue().danglingVertices;
+            Deque<List<PathPatternExpr>> pathPatterns = e.getValue().pathPatterns;
+            return new StructureContext(edgeDependencyGraph, pathPatterns, danglingVertices, graphIdentifier);
         }));
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementResolutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java
similarity index 64%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementResolutionVisitor.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java
index 4f3c6f9..38bebe9 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementResolutionVisitor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/StructureResolutionVisitor.java
@@ -18,49 +18,51 @@
  */
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
+import static org.apache.asterix.graphix.extension.GraphixMetadataExtension.getGraph;
+
+import java.util.Map;
+
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.graphix.algebra.compiler.provider.GraphixCompilationProvider;
-import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
 import org.apache.asterix.graphix.lang.clause.FromGraphClause;
 import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.rewrites.resolve.IGraphElementResolver;
 import org.apache.asterix.graphix.lang.rewrites.resolve.InferenceBasedResolver;
-import org.apache.asterix.graphix.lang.rewrites.resolve.NoResolutionResolver;
 import org.apache.asterix.graphix.lang.rewrites.resolve.SchemaKnowledgeTable;
+import org.apache.asterix.graphix.lang.statement.DeclareGraphStatement;
 import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
-import org.apache.asterix.graphix.lang.struct.ElementLabel;
 import org.apache.asterix.graphix.metadata.entity.schema.Graph;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.api.exceptions.IWarningCollector;
-import org.apache.hyracks.api.exceptions.Warning;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 
 /**
  * Resolve graph element labels and edge directions in our AST. We assume that all graph elements have a variable.
  *
- * @see NoResolutionResolver
  * @see InferenceBasedResolver
  */
-public class ElementResolutionVisitor extends AbstractGraphixQueryVisitor {
-    private static final Logger LOGGER = LogManager.getLogger(ElementResolutionVisitor.class);
+public class StructureResolutionVisitor extends AbstractGraphixQueryVisitor {
+    private static final Logger LOGGER = LogManager.getLogger(StructureResolutionVisitor.class);
 
     // If we exceed 500 iterations, something is probably wrong... log this.
     private static final long DEFAULT_RESOLVER_ITERATION_MAX = 500;
 
     private final MetadataProvider metadataProvider;
-    private final IWarningCollector warningCollector;
+    private final Map<GraphIdentifier, DeclareGraphStatement> declaredGraphs;
 
-    public ElementResolutionVisitor(MetadataProvider metadataProvider, IWarningCollector warningCollector) {
-        this.metadataProvider = metadataProvider;
-        this.warningCollector = warningCollector;
+    public StructureResolutionVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.declaredGraphs = graphixRewritingContext.getDeclaredGraphs();
+        this.metadataProvider = graphixRewritingContext.getMetadataProvider();
     }
 
     @Override
@@ -72,19 +74,27 @@
                     ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
             Identifier graphName = fromGraphClause.getGraphName();
 
-            // Fetch the graph from our metadata.
-            try {
-                Graph graphFromMetadata = GraphixMetadataExtension.getGraph(metadataProvider.getMetadataTxnContext(),
-                        dataverseName, graphName.getValue());
-                if (graphFromMetadata == null) {
+            // First, try to find our graph inside our declared graph set.
+            GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName.getValue());
+            DeclareGraphStatement declaredGraph = declaredGraphs.get(graphIdentifier);
+            if (declaredGraph != null) {
+                schemaKnowledgeTable = new SchemaKnowledgeTable(declaredGraph.getGraphConstructor());
+
+            } else {
+                // Otherwise, fetch the graph from our metadata.
+                try {
+                    MetadataTransactionContext metadataTxnContext = metadataProvider.getMetadataTxnContext();
+                    Graph graphFromMetadata = getGraph(metadataTxnContext, dataverseName, graphName.getValue());
+                    if (graphFromMetadata == null) {
+                        throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                                "Graph " + graphName.getValue() + " does not exist.");
+                    }
+                    schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
+
+                } catch (AlgebricksException e) {
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
                             "Graph " + graphName.getValue() + " does not exist.");
                 }
-                schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
-
-            } catch (AlgebricksException e) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
-                        "Graph " + graphName.getValue() + " does not exist.");
             }
 
         } else {
@@ -96,10 +106,7 @@
         String resolverMetadataKeyName = GraphixCompilationProvider.RESOLVER_METADATA_CONFIG;
         if (metadataProvider.getConfig().containsKey(resolverMetadataKeyName)) {
             String resolverProperty = metadataProvider.getProperty(resolverMetadataKeyName);
-            if (resolverProperty.equalsIgnoreCase(NoResolutionResolver.METADATA_CONFIG_NAME)) {
-                graphElementResolver = new NoResolutionResolver();
-
-            } else if (resolverProperty.equalsIgnoreCase(InferenceBasedResolver.METADATA_CONFIG_NAME)) {
+            if (resolverProperty.equalsIgnoreCase(InferenceBasedResolver.METADATA_CONFIG_NAME)) {
                 graphElementResolver = new InferenceBasedResolver(schemaKnowledgeTable);
 
             } else {
@@ -132,23 +139,12 @@
             }
         }
 
-        // Perform the final pass of our FROM-GRAPH-CLAUSE. Raise warnings if necessary.
+        // Perform the final pass of our FROM-GRAPH-CLAUSE.
         new AbstractGraphixQueryVisitor() {
             @Override
             public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
-                Identifier vertexIdentifier = vertexPatternExpr.getVariableExpr().getVar();
                 if (vertexPatternExpr.getLabels().isEmpty()) {
                     vertexPatternExpr.getLabels().addAll(schemaKnowledgeTable.getVertexLabelSet());
-                    if (schemaKnowledgeTable.getVertexLabelSet().size() > 1 && warningCollector.shouldWarn()) {
-                        warningCollector.warn(Warning.of(vertexPatternExpr.getSourceLocation(),
-                                ErrorCode.COMPILATION_ERROR, "Vertex label could not be resolved. Assuming that"
-                                        + " vertex " + vertexIdentifier + " has all schema labels."));
-                    }
-
-                } else if (vertexPatternExpr.getLabels().stream().allMatch(ElementLabel::isInferred)
-                        && vertexPatternExpr.getLabels().size() > 1) {
-                    warningCollector.warn(Warning.of(vertexPatternExpr.getSourceLocation(), ErrorCode.COMPILATION_ERROR,
-                            "More than one label has been resolved for vertex " + vertexIdentifier + "."));
                 }
                 return vertexPatternExpr;
             }
@@ -156,18 +152,8 @@
             @Override
             public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
                 EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
-                Identifier edgeIdentifier = edgeDescriptor.getVariableExpr().getVar();
                 if (edgeDescriptor.getEdgeLabels().isEmpty()) {
                     edgeDescriptor.getEdgeLabels().addAll(schemaKnowledgeTable.getEdgeLabelSet());
-                    if (edgeDescriptor.getEdgeLabels().size() > 1 && warningCollector.shouldWarn()) {
-                        warningCollector.warn(Warning.of(edgePatternExpr.getSourceLocation(),
-                                ErrorCode.COMPILATION_ERROR, "Edge label for " + edgeIdentifier + " could not be"
-                                        + " resolved. Assuming that this edge has all schema labels."));
-                    }
-                } else if (edgeDescriptor.getEdgeLabels().stream().allMatch(ElementLabel::isInferred)
-                        && edgeDescriptor.getEdgeLabels().size() > 1) {
-                    warningCollector.warn(Warning.of(edgePatternExpr.getSourceLocation(), ErrorCode.COMPILATION_ERROR,
-                            "More than one label has been resolved for edge " + edgeIdentifier + "."));
                 }
                 for (VertexPatternExpr internalVertex : edgePatternExpr.getInternalVertices()) {
                     internalVertex.accept(this, arg);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java
new file mode 100644
index 0000000..2f99a94
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/VariableSubstitutionVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.lang.sqlpp.util.SqlppRewriteUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
+
+/**
+ * Substitute qualifying {@link VariableExpr} nodes (via their {@link VarIdentifier}) with a deep-copy of an expression.
+ */
+public class VariableSubstitutionVisitor extends AbstractSqlppExpressionScopingVisitor {
+    private final Map<VarIdentifier, Expression> substitutionMap = new HashMap<>();
+
+    public VariableSubstitutionVisitor(LangRewritingContext context) {
+        super(context);
+    }
+
+    public void addSubstitution(VarIdentifier varIdentifier, Expression substitution) {
+        substitutionMap.put(varIdentifier, substitution);
+    }
+
+    @Override
+    public Expression visit(VariableExpr variableExpr, ILangExpression arg) throws CompilationException {
+        Expression substitution = substitutionMap.getOrDefault(variableExpr.getVar(), variableExpr);
+        if (substitution != null) {
+            substitution = (Expression) SqlppRewriteUtil.deepCopy(substitution);
+        }
+        return substitution;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java
new file mode 100644
index 0000000..b5fd970
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/DeclareGraphStatement.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.statement;
+
+import java.util.Objects;
+
+import org.apache.asterix.algebra.extension.ExtensionStatement;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.app.translator.GraphixQueryTranslator;
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.translator.IRequestParameters;
+import org.apache.asterix.translator.IStatementExecutor;
+import org.apache.hyracks.api.client.IHyracksClientConnection;
+
+/**
+ * Statement for storing a {@link GraphConstructor} instance in our _context_ instead of our metadata.
+ */
+public class DeclareGraphStatement extends ExtensionStatement {
+    private final GraphConstructor graphConstructor;
+    private final String graphName;
+
+    // Our dataverse name is taken from the active declared dataverse.
+    private DataverseName dataverseName;
+
+    public DeclareGraphStatement(String graphName, GraphConstructor graphConstructor) {
+        this.graphName = Objects.requireNonNull(graphName);
+        this.graphConstructor = Objects.requireNonNull(graphConstructor);
+    }
+
+    public String getGraphName() {
+        return graphName;
+    }
+
+    public DataverseName getDataverseName() {
+        return dataverseName;
+    }
+
+    public GraphConstructor getGraphConstructor() {
+        return graphConstructor;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+
+    @Override
+    public byte getCategory() {
+        return Category.DDL;
+    }
+
+    @Override
+    public String getName() {
+        return DeclareGraphStatement.class.getName();
+    }
+
+    @Override
+    public void handle(IHyracksClientConnection hcc, IStatementExecutor statementExecutor,
+            IRequestParameters requestParameters, MetadataProvider metadataProvider, int resultSetId) throws Exception {
+        this.dataverseName = statementExecutor.getActiveDataverseName(null);
+        metadataProvider.validateDatabaseObjectName(dataverseName, graphName, this.getSourceLocation());
+        GraphixQueryTranslator graphixQueryTranslator = (GraphixQueryTranslator) statementExecutor;
+        graphixQueryTranslator.addDeclaredGraph(this);
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDecl.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
similarity index 69%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDecl.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
index 5146de1..790a6be 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDecl.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphElementDeclaration.java
@@ -18,8 +18,6 @@
  */
 package org.apache.asterix.graphix.lang.statement;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Objects;
 
 import org.apache.asterix.algebra.extension.ExtensionStatement;
@@ -36,21 +34,17 @@
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 
 /**
- * A declaration for a single graph element (vertex or edge), which cannot be explicitly specified by the user.
- * - This is analogous to {@link org.apache.asterix.lang.common.statement.ViewDecl} for views and
- * {@link org.apache.asterix.lang.common.statement.FunctionDecl} for functions, in that we use this class to store the
- * directly parsed AST and a normalized AST for the bodies themselves.
- * - Unlike views and functions, a single graph element may have more than one body. Graph element declarations start
- * off with one body, and it is up to the caller to manage multiple bodies.
+ * A declaration for a single graph element (vertex or edge), which cannot be explicitly specified by the user. We
+ * use this class to store the directly parsed AST and a normalized AST for the bodies themselves.
  */
-public final class GraphElementDecl extends ExtensionStatement {
+public final class GraphElementDeclaration extends ExtensionStatement {
     private final GraphElementIdentifier identifier;
-    private final List<Expression> bodies = new ArrayList<>();
-    private final List<Expression> normalizedBodies = new ArrayList<>();
+    private final Expression rawBody;
+    private Expression normalizedBody;
 
-    public GraphElementDecl(GraphElementIdentifier identifier, Expression body) {
+    public GraphElementDeclaration(GraphElementIdentifier identifier, Expression rawBody) {
         this.identifier = Objects.requireNonNull(identifier);
-        this.bodies.add(Objects.requireNonNull(body));
+        this.rawBody = Objects.requireNonNull(rawBody);
     }
 
     public GraphElementIdentifier getIdentifier() {
@@ -61,12 +55,16 @@
         return identifier.getGraphIdentifier();
     }
 
-    public List<Expression> getBodies() {
-        return bodies;
+    public Expression getRawBody() {
+        return rawBody;
     }
 
-    public List<Expression> getNormalizedBodies() {
-        return normalizedBodies;
+    public Expression getNormalizedBody() {
+        return normalizedBody;
+    }
+
+    public void setNormalizedBody(Expression normalizedBody) {
+        this.normalizedBody = normalizedBody;
     }
 
     @Override
@@ -81,13 +79,13 @@
 
     @Override
     public String getName() {
-        return GraphElementDecl.class.getName();
+        return GraphElementDeclaration.class.getName();
     }
 
     @Override
     public void handle(IHyracksClientConnection hcc, IStatementExecutor statementExecutor,
             IRequestParameters requestParameters, MetadataProvider metadataProvider, int resultSetId) throws Exception {
         throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, getSourceLocation(),
-                "Handling a GraphElementDecl (this should not be possible).");
+                "Handling a GraphElementDeclaration (this should not be possible).");
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
index c21e30c..2898a16 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.graphix.lang.struct;
 
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -94,6 +95,28 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(edgeDirection, patternType, edgeLabels, variableExpr, minimumHops, maximumHops);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof EdgeDescriptor)) {
+            return false;
+        }
+        EdgeDescriptor that = (EdgeDescriptor) object;
+        return Objects.equals(this.edgeDirection, that.edgeDirection)
+                && Objects.equals(this.patternType, that.patternType)
+                && Objects.equals(this.edgeLabels, that.edgeLabels)
+                && Objects.equals(this.variableExpr, that.variableExpr)
+                && Objects.equals(this.minimumHops, that.minimumHops)
+                && Objects.equals(this.maximumHops, that.maximumHops);
+    }
+
+    @Override
     public String toString() {
         String labelsString = edgeLabels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
         String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
index 9678ed8..c4cc975 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/util/GraphStatementHandlingUtil.java
@@ -19,9 +19,9 @@
 package org.apache.asterix.graphix.lang.util;
 
 import java.rmi.RemoteException;
+import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.Map;
+import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 
@@ -39,7 +39,7 @@
 import org.apache.asterix.graphix.lang.rewrites.visitor.AbstractGraphixQueryVisitor;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.statement.GraphElementDeclaration;
 import org.apache.asterix.graphix.metadata.entity.dependency.DependencyIdentifier;
 import org.apache.asterix.graphix.metadata.entity.dependency.GraphRequirements;
 import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
@@ -133,30 +133,25 @@
         // Build the graph schema.
         GraphIdentifier graphIdentifier = new GraphIdentifier(activeDataverseName, cgs.getGraphName());
         Schema.Builder schemaBuilder = new Schema.Builder(graphIdentifier);
-        Map<GraphElementIdentifier, GraphElementDecl> graphElementDecls = new LinkedHashMap<>();
+        List<GraphElementDeclaration> graphElementDeclarations = new ArrayList<>();
         for (GraphConstructor.VertexConstructor vertex : cgs.getVertexElements()) {
             Vertex schemaVertex =
                     schemaBuilder.addVertex(vertex.getLabel(), vertex.getPrimaryKeyFields(), vertex.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
                     GraphElementIdentifier id = schemaVertex.getIdentifier();
-                    if (graphElementDecls.containsKey(id)) {
-                        graphElementDecls.get(id).getBodies().add(vertex.getExpression());
-
-                    } else {
-                        GraphElementDecl decl = new GraphElementDecl(id, vertex.getExpression());
-                        decl.setSourceLocation(vertex.getSourceLocation());
-                        graphElementDecls.put(schemaVertex.getIdentifier(), decl);
-                    }
+                    GraphElementDeclaration decl = new GraphElementDeclaration(id, vertex.getExpression());
+                    decl.setSourceLocation(vertex.getSourceLocation());
+                    graphElementDeclarations.add(decl);
                     break;
 
-                case CONFLICTING_PRIMARY_KEY:
+                case VERTEX_LABEL_CONFLICT:
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertex.getSourceLocation(),
-                            "Conflicting primary keys for vertices with label " + vertex.getLabel());
+                            "Conflicting vertex label found: " + vertex.getLabel());
 
                 default:
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, vertex.getSourceLocation(),
-                            "Constructor vertex was not returned, but the error is not a conflicting primary key!");
+                            "Constructor vertex was not returned, but the error is not a vertex label conflict!");
             }
         }
         for (GraphConstructor.EdgeConstructor edge : cgs.getEdgeElements()) {
@@ -165,17 +160,10 @@
                             edge.getDestinationKeyFields(), edge.getSourceKeyFields(), edge.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
-                    if (edge.getDefinition() != null) {
-                        GraphElementIdentifier id = schemaEdge.getIdentifier();
-                        if (graphElementDecls.containsKey(id)) {
-                            graphElementDecls.get(id).getBodies().add(edge.getExpression());
-
-                        } else {
-                            GraphElementDecl decl = new GraphElementDecl(id, edge.getExpression());
-                            decl.setSourceLocation(edge.getSourceLocation());
-                            graphElementDecls.put(id, decl);
-                        }
-                    }
+                    GraphElementIdentifier id = schemaEdge.getIdentifier();
+                    GraphElementDeclaration decl = new GraphElementDeclaration(id, edge.getExpression());
+                    decl.setSourceLocation(edge.getSourceLocation());
+                    graphElementDeclarations.add(decl);
                     break;
 
                 case SOURCE_VERTEX_NOT_FOUND:
@@ -188,10 +176,9 @@
                             "Destination vertex " + edge.getDestinationLabel() + " not found in the edge "
                                     + edge.getEdgeLabel() + ".");
 
-                case CONFLICTING_SOURCE_KEY:
-                case CONFLICTING_DESTINATION_KEY:
+                case EDGE_LABEL_CONFLICT:
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
-                            "Conflicting edge with the same label found: " + edge.getEdgeLabel());
+                            "Conflicting edge label found: " + edge.getEdgeLabel());
 
                 default:
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, edge.getSourceLocation(),
@@ -203,22 +190,18 @@
         metadataProvider.setDefaultDataverse(dataverse);
         DataverseName dataverseName = (cgs.getDataverseName() != null) ? cgs.getDataverseName() : activeDataverseName;
         GraphRequirements requirements = new GraphRequirements(dataverseName, cgs.getGraphName());
-        for (GraphElementDecl graphElementDecl : graphElementDecls.values()) {
+        for (GraphElementDeclaration graphElementDeclaration : graphElementDeclarations) {
             // Determine the graph dependencies using the raw body.
             Set<DependencyIdentifier> graphDependencies = new HashSet<>();
-            for (Expression rawBody : graphElementDecl.getBodies()) {
-                collectDependenciesOnGraph(rawBody, activeDataverseName, graphDependencies);
-            }
+            collectDependenciesOnGraph(graphElementDeclaration.getRawBody(), activeDataverseName, graphDependencies);
             requirements.loadGraphDependencies(graphDependencies);
 
             // Verify that each element definition is usable.
             ((GraphixQueryTranslator) statementExecutor).setGraphElementNormalizedBody(metadataProvider,
-                    graphElementDecl, graphixQueryRewriter);
+                    graphElementDeclaration, graphixQueryRewriter);
 
             // Determine the non-graph dependencies using the normalized body.
-            for (Expression normalizedBody : graphElementDecl.getNormalizedBodies()) {
-                requirements.loadNonGraphDependencies(normalizedBody, graphixQueryRewriter);
-            }
+            requirements.loadNonGraphDependencies(graphElementDeclaration.getNormalizedBody(), graphixQueryRewriter);
         }
 
         // Add / upsert our graph + requirements to our metadata.
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixIndexDetailProvider.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixIndexDetailProvider.java
index 942c0a8..90a2953 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixIndexDetailProvider.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixIndexDetailProvider.java
@@ -37,7 +37,7 @@
 /**
  * Provide detail about two metadata extension indexes: Graph and GraphDependency.
  */
-public class GraphixIndexDetailProvider {
+public final class GraphixIndexDetailProvider {
     public static IGraphixIndexDetail<Graph> getGraphIndexDetail() {
         return graphIndexDetail;
     }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixRecordDetailProvider.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixRecordDetailProvider.java
index 0d99dc6..1eac643 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixRecordDetailProvider.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixRecordDetailProvider.java
@@ -38,7 +38,6 @@
  */
 public final class GraphixRecordDetailProvider {
     public static final String FIELD_NAME_BODY = "Body";
-    public static final String FIELD_NAME_DEFINITIONS = "Definitions";
     public static final String FIELD_NAME_DEPENDENCY_ID = "DependencyID";
     public static final String FIELD_NAME_DESTINATION_KEY = "DestinationKey";
     public static final String FIELD_NAME_DESTINATION_LABEL = "DestinationLabel";
@@ -51,18 +50,10 @@
     public static final String FIELD_NAME_SOURCE_LABEL = "SourceLabel";
     public static final String FIELD_NAME_VERTICES = "Vertices";
 
-    public static IRecordTypeDetail getVertexDefRecordDetail() {
-        return vertexDefRecordDetail;
-    }
-
     public static IRecordTypeDetail getVertexRecordDetail() {
         return vertexRecordDetail;
     }
 
-    public static IRecordTypeDetail getEdgeDefRecordDetail() {
-        return edgeDefRecordDetail;
-    }
-
     public static IRecordTypeDetail getEdgeRecordDetail() {
         return edgeRecordDetail;
     }
@@ -99,41 +90,12 @@
         }
     }
 
-    // Record type for a definition inside a Vertex object attached to a Graph.
-    private static final IRecordTypeDetail vertexDefRecordDetail = new AbstractRecordTypeDetail() {
-        { // Construct our record type here.
-            String[] fieldNames = new String[] { FIELD_NAME_PRIMARY_KEY, FIELD_NAME_BODY };
-            IAType[] fieldTypes = new IAType[] {
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null), BuiltinType.ASTRING };
-            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
-        }
-
-        @Override
-        public String getRecordTypeName() {
-            return "VertexDefinitionRecordType";
-        }
-
-        @Override
-        public int getIndexForField(String fieldName) {
-            switch (fieldName) {
-                case FIELD_NAME_PRIMARY_KEY:
-                    return 0;
-
-                case FIELD_NAME_BODY:
-                    return 1;
-
-                default:
-                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
-            }
-        }
-    };
-
     // Record type for a Vertex object attached to a Graph.
     private static final IRecordTypeDetail vertexRecordDetail = new AbstractRecordTypeDetail() {
         { // Construct our record type here.
-            String[] fieldNames = new String[] { FIELD_NAME_LABEL, FIELD_NAME_DEFINITIONS };
+            String[] fieldNames = new String[] { FIELD_NAME_LABEL, FIELD_NAME_PRIMARY_KEY, FIELD_NAME_BODY };
             IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING,
-                    new AOrderedListType(vertexDefRecordDetail.getRecordType(), null) };
+                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null), BuiltinType.ASTRING };
             recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
         }
 
@@ -148,37 +110,7 @@
                 case FIELD_NAME_LABEL:
                     return 0;
 
-                case FIELD_NAME_DEFINITIONS:
-                    return 1;
-
-                default:
-                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
-            }
-        }
-    };
-
-    // Record type for a definition inside an Edge object attached to a Graph.
-    private static final IRecordTypeDetail edgeDefRecordDetail = new AbstractRecordTypeDetail() {
-        { // Construct our record type here.
-            String[] fieldNames = new String[] { FIELD_NAME_SOURCE_KEY, FIELD_NAME_DESTINATION_KEY, FIELD_NAME_BODY };
-            IAType[] fieldTypes = new IAType[] {
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null), BuiltinType.ASTRING };
-            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
-        }
-
-        @Override
-        public String getRecordTypeName() {
-            return "EdgeDefinitionRecordType";
-        }
-
-        @Override
-        public int getIndexForField(String fieldName) {
-            switch (fieldName) {
-                case FIELD_NAME_SOURCE_KEY:
-                    return 0;
-
-                case FIELD_NAME_DESTINATION_KEY:
+                case FIELD_NAME_PRIMARY_KEY:
                     return 1;
 
                 case FIELD_NAME_BODY:
@@ -194,9 +126,10 @@
     private static final IRecordTypeDetail edgeRecordDetail = new AbstractRecordTypeDetail() {
         { // Construct our record type here.
             String[] fieldNames = new String[] { FIELD_NAME_LABEL, FIELD_NAME_DESTINATION_LABEL,
-                    FIELD_NAME_SOURCE_LABEL, FIELD_NAME_DEFINITIONS };
+                    FIELD_NAME_SOURCE_LABEL, FIELD_NAME_SOURCE_KEY, FIELD_NAME_DESTINATION_KEY, FIELD_NAME_BODY };
             IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.ASTRING,
-                    new AOrderedListType(edgeDefRecordDetail.getRecordType(), null) };
+                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
+                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null), BuiltinType.ASTRING };
             recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
         }
 
@@ -217,9 +150,15 @@
                 case FIELD_NAME_SOURCE_LABEL:
                     return 2;
 
-                case FIELD_NAME_DEFINITIONS:
+                case FIELD_NAME_SOURCE_KEY:
                     return 3;
 
+                case FIELD_NAME_DESTINATION_KEY:
+                    return 4;
+
+                case FIELD_NAME_BODY:
+                    return 5;
+
                 default:
                     throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
             }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
index 2cd9036..1c4cc17 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
@@ -18,10 +18,8 @@
  */
 package org.apache.asterix.graphix.metadata.entity.schema;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.stream.Collectors;
 
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
@@ -29,27 +27,33 @@
 /**
  * Metadata representation of an edge. An edge consists of the following:
  * 1. A {@link GraphElementIdentifier}, to uniquely identify the edge across other graph elements.
- * 2. A source vertex ({@link Vertex}) reference.
- * 3. A destination vertex ({@link Vertex}) reference.
- * 4. A collection of edge definitions, which contains a source key, destination key, and a SQL++ string.
+ * 2. An {@link ElementLabel} instance associated with the source vertex.
+ * 3. An {@link ElementLabel} instance associated with the destination vertex.
+ * 4. A list of source key fields, associated with the definition body.
+ * 5. A list of destination key fields, associated with the definition body.
+ * 6. A SQL++ string denoting the definition body.
  */
-public class Edge implements Element {
+public class Edge implements IElement {
     private static final long serialVersionUID = 1L;
 
     private final GraphElementIdentifier identifier;
-    private final Vertex destinationVertex;
-    private final Vertex sourceVertex;
-    private final List<Definition> definitions;
+    private final ElementLabel sourceVertexLabel;
+    private final ElementLabel destinationVertexLabel;
+    private final List<List<String>> sourceKeyFieldNames;
+    private final List<List<String>> destinationKeyFieldNames;
+    private final String definitionBody;
 
     /**
      * Use {@link Schema.Builder} to build Edge instances instead of this constructor.
      */
-    Edge(GraphElementIdentifier identifier, Vertex destinationVertex, Vertex sourceVertex, Definition edgeDefinition) {
-        this.destinationVertex = Objects.requireNonNull(destinationVertex);
-        this.sourceVertex = Objects.requireNonNull(sourceVertex);
+    Edge(GraphElementIdentifier identifier, ElementLabel sourceVertexLabel, ElementLabel destinationVertexLabel,
+            List<List<String>> sourceKeyFieldNames, List<List<String>> destKeyFieldNames, String definitionBody) {
         this.identifier = Objects.requireNonNull(identifier);
-        this.definitions = new ArrayList<>();
-        this.definitions.add(Objects.requireNonNull(edgeDefinition));
+        this.sourceVertexLabel = Objects.requireNonNull(sourceVertexLabel);
+        this.destinationVertexLabel = Objects.requireNonNull(destinationVertexLabel);
+        this.sourceKeyFieldNames = Objects.requireNonNull(sourceKeyFieldNames);
+        this.destinationKeyFieldNames = Objects.requireNonNull(destKeyFieldNames);
+        this.definitionBody = Objects.requireNonNull(definitionBody);
     }
 
     public static class Definition {
@@ -78,23 +82,19 @@
     }
 
     public ElementLabel getDestinationLabel() {
-        return destinationVertex.getLabel();
+        return destinationVertexLabel;
     }
 
     public ElementLabel getSourceLabel() {
-        return sourceVertex.getLabel();
+        return sourceVertexLabel;
     }
 
-    public Vertex getDestinationVertex() {
-        return destinationVertex;
+    public List<List<String>> getSourceKeyFieldNames() {
+        return sourceKeyFieldNames;
     }
 
-    public Vertex getSourceVertex() {
-        return sourceVertex;
-    }
-
-    public List<Definition> getDefinitions() {
-        return definitions;
+    public List<List<String>> getDestinationKeyFieldNames() {
+        return destinationKeyFieldNames;
     }
 
     @Override
@@ -108,8 +108,8 @@
     }
 
     @Override
-    public List<String> getDefinitionBodies() {
-        return definitions.stream().map(Definition::getDefinition).collect(Collectors.toList());
+    public String getDefinitionBody() {
+        return definitionBody;
     }
 
     @Override
@@ -118,6 +118,6 @@
         String sourceNodePattern = "(:" + getSourceLabel() + ")";
         String destinationNodePattern = "(:" + getDestinationLabel() + ")";
         String edgePattern = sourceNodePattern + "-" + edgeBodyPattern + "->" + destinationNodePattern;
-        return edgePattern + " AS " + String.join(",\n", getDefinitionBodies());
+        return edgePattern + " AS " + definitionBody;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Element.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
similarity index 88%
rename from asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Element.java
rename to asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
index e0123bc..3637a45 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Element.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/IElement.java
@@ -19,7 +19,6 @@
 package org.apache.asterix.graphix.metadata.entity.schema;
 
 import java.io.Serializable;
-import java.util.List;
 
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
@@ -28,12 +27,12 @@
  * Metadata interface for a graph element (i.e. edge or vertex). An element has the following:
  * 1. A {@link GraphElementIdentifier}, to uniquely identify the element across other graph elements.
  * 2. A {@link ElementLabel} unique amongst the element classes (e.g. an edge label is unique amongst all graph edges).
- * 3. A non-empty list of SQL++ strings, each of which represents a definition.
+ * 3. A non-null SQL++ string, representing a graph element body.
  */
-public interface Element extends Serializable {
+public interface IElement extends Serializable {
     GraphElementIdentifier getIdentifier();
 
     ElementLabel getLabel();
 
-    List<String> getDefinitionBodies();
+    String getDefinitionBody();
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
index b4b965a..d19590f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
@@ -18,6 +18,9 @@
  */
 package org.apache.asterix.graphix.metadata.entity.schema;
 
+import static org.apache.asterix.graphix.common.metadata.GraphElementIdentifier.Kind.EDGE;
+import static org.apache.asterix.graphix.common.metadata.GraphElementIdentifier.Kind.VERTEX;
+
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -69,34 +72,25 @@
         }
 
         /**
-         * @return Null if the primary keys of an existing vertex conflict with the vertex to-be-added. The vertex
-         * to-be-added otherwise.
+         * @return Null if a vertex with the same label already exists. The vertex to-be-added otherwise.
          */
         public Vertex addVertex(ElementLabel vertexLabel, List<List<String>> primaryKeyFieldNames, String definition) {
             if (!vertexLabelMap.containsKey(vertexLabel)) {
-                GraphElementIdentifier identifier =
-                        new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.VERTEX, vertexLabel);
-                Vertex newVertex = new Vertex(identifier, new Vertex.Definition(primaryKeyFieldNames, definition));
+                GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier, VERTEX, vertexLabel);
+                Vertex newVertex = new Vertex(identifier, primaryKeyFieldNames, definition);
                 workingSchema.vertexList.add(newVertex);
                 vertexLabelMap.put(vertexLabel, newVertex);
                 return newVertex;
 
             } else {
-                Vertex existingVertex = vertexLabelMap.get(vertexLabel);
-                if (!existingVertex.getPrimaryKeyFieldNames().equals(primaryKeyFieldNames)) {
-                    lastError = Error.CONFLICTING_PRIMARY_KEY;
-                    return null;
-                }
-                existingVertex.getDefinitions().add(new Vertex.Definition(primaryKeyFieldNames, definition));
-                return existingVertex;
+                lastError = Error.VERTEX_LABEL_CONFLICT;
+                return null;
             }
         }
 
         /**
-         * @return Null if there exists no vertex with the given source label or destination label, OR if the
-         * source vertex / destination vertex of an existing edge conflict with the edge to-be-added, OR if the source
-         * key / destination key of an existing edge conflict with the edge to-be-added. Otherwise, the edge
-         * to-be-added.
+         * @return Null if there exists no vertex with the given source label or destination label, OR if an edge with
+         * the same label already exists. The edge to-be-added otherwise.
          */
         public Edge addEdge(ElementLabel edgeLabel, ElementLabel destinationLabel, ElementLabel sourceLabel,
                 List<List<String>> destinationKeyFieldNames, List<List<String>> sourceKeyFieldNames,
@@ -108,37 +102,16 @@
             } else if (!vertexLabelMap.containsKey(destinationLabel)) {
                 lastError = Error.DESTINATION_VERTEX_NOT_FOUND;
                 return null;
-            }
 
-            if (edgeLabelMap.containsKey(edgeLabel)) {
-                for (Edge existingEdge : edgeLabelMap.get(edgeLabel)) {
-                    ElementLabel existingSourceLabel = existingEdge.getSourceLabel();
-                    ElementLabel existingDestLabel = existingEdge.getDestinationLabel();
-                    if (existingSourceLabel.equals(sourceLabel) && existingDestLabel.equals(destinationLabel)) {
-                        Edge.Definition existingDefinition = existingEdge.getDefinitions().get(0);
-                        if (!existingDefinition.getSourceKeyFieldNames().equals(sourceKeyFieldNames)) {
-                            lastError = Error.CONFLICTING_SOURCE_KEY;
-                            return null;
-
-                        } else if (!existingDefinition.getDestinationKeyFieldNames().equals(destinationKeyFieldNames)) {
-                            lastError = Error.CONFLICTING_DESTINATION_KEY;
-                            return null;
-
-                        } else {
-                            Edge.Definition newEdgeDefinition =
-                                    new Edge.Definition(destinationKeyFieldNames, sourceKeyFieldNames, definitionBody);
-                            existingEdge.getDefinitions().add(newEdgeDefinition);
-                            return existingEdge;
-                        }
-                    }
-                }
+            } else if (edgeLabelMap.containsKey(edgeLabel)) {
+                lastError = Error.EDGE_LABEL_CONFLICT;
+                return null;
             }
 
             // Update our schema.
-            GraphElementIdentifier identifier =
-                    new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.EDGE, edgeLabel);
-            Edge newEdge = new Edge(identifier, vertexLabelMap.get(destinationLabel), vertexLabelMap.get(sourceLabel),
-                    new Edge.Definition(destinationKeyFieldNames, sourceKeyFieldNames, definitionBody));
+            GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier, EDGE, edgeLabel);
+            Edge newEdge = new Edge(identifier, sourceLabel, destinationLabel, sourceKeyFieldNames,
+                    destinationKeyFieldNames, definitionBody);
             workingSchema.edgeList.add(newEdge);
 
             // Update our edge label map.
@@ -158,11 +131,8 @@
 
         public enum Error {
             NO_ERROR,
-
-            CONFLICTING_PRIMARY_KEY,
-            CONFLICTING_SOURCE_KEY,
-            CONFLICTING_DESTINATION_KEY,
-
+            VERTEX_LABEL_CONFLICT,
+            EDGE_LABEL_CONFLICT,
             SOURCE_VERTEX_NOT_FOUND,
             DESTINATION_VERTEX_NOT_FOUND
         }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
index e6950e3..dd4a21e 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
@@ -18,10 +18,8 @@
  */
 package org.apache.asterix.graphix.metadata.entity.schema;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
-import java.util.stream.Collectors;
 
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 import org.apache.asterix.graphix.lang.struct.ElementLabel;
@@ -29,53 +27,27 @@
 /**
  * Metadata representation of a vertex. A vertex consists of the following:
  * 1. A {@link GraphElementIdentifier}, to uniquely identify the vertex across other graph elements.
- * 2. A collection of vertex definitions, each of which consists of a primary key and a SQL++ string.
+ * 2. A list of primary key fields, associated with the definition body.
+ * 3. A SQL++ string denoting the definition body.
  */
-public class Vertex implements Element {
+public class Vertex implements IElement {
     private static final long serialVersionUID = 1L;
 
     private final GraphElementIdentifier identifier;
-    private final List<Definition> definitions;
+    private final List<List<String>> primaryKeyFieldNames;
+    private final String definitionBody;
 
     /**
      * Use {@link Schema.Builder} to build Vertex instances instead of this constructor.
      */
-    Vertex(GraphElementIdentifier identifier, Definition definition) {
+    Vertex(GraphElementIdentifier identifier, List<List<String>> primaryKeyFieldNames, String definitionBody) {
         this.identifier = Objects.requireNonNull(identifier);
-        this.definitions = new ArrayList<>();
-        this.definitions.add(Objects.requireNonNull(definition));
+        this.primaryKeyFieldNames = primaryKeyFieldNames;
+        this.definitionBody = Objects.requireNonNull(definitionBody);
     }
 
-    public static class Definition {
-        private final List<List<String>> primaryKeyFieldNames;
-        private final String definition;
-
-        Definition(List<List<String>> primaryKeyFieldNames, String definition) {
-            this.primaryKeyFieldNames = primaryKeyFieldNames;
-            this.definition = definition;
-        }
-
-        public List<List<String>> getPrimaryKeyFieldNames() {
-            return primaryKeyFieldNames;
-        }
-
-        public String getDefinition() {
-            return definition;
-        }
-
-        @Override
-        public String toString() {
-            return definition;
-        }
-    }
-
-    // A primary key is the same across all vertex definitions.
     public List<List<String>> getPrimaryKeyFieldNames() {
-        return definitions.get(0).getPrimaryKeyFieldNames();
-    }
-
-    public List<Definition> getDefinitions() {
-        return definitions;
+        return primaryKeyFieldNames;
     }
 
     @Override
@@ -89,12 +61,12 @@
     }
 
     @Override
-    public List<String> getDefinitionBodies() {
-        return definitions.stream().map(Definition::getDefinition).collect(Collectors.toList());
+    public String getDefinitionBody() {
+        return definitionBody;
     }
 
     @Override
     public String toString() {
-        return "(:" + getLabel() + ") AS " + String.join(",\n", getDefinitionBodies());
+        return "(:" + getLabel() + ") AS " + definitionBody;
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
index 8702cd5..446ae27 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/GraphTupleTranslator.java
@@ -19,7 +19,6 @@
 package org.apache.asterix.graphix.metadata.entitytupletranslators;
 
 import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_BODY;
-import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_DEFINITIONS;
 import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_DESTINATION_KEY;
 import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_DESTINATION_LABEL;
 import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_EDGES;
@@ -67,9 +66,7 @@
     private static final int GRAPH_PAYLOAD_TUPLE_FIELD_INDEX = 2;
 
     // We are interested in the detail of the following records.
-    private static final IRecordTypeDetail EDEF_RECORD_DETAIL = GraphixRecordDetailProvider.getEdgeDefRecordDetail();
     private static final IRecordTypeDetail EDGE_RECORD_DETAIL = GraphixRecordDetailProvider.getEdgeRecordDetail();
-    private static final IRecordTypeDetail VDEF_RECORD_DETAIL = GraphixRecordDetailProvider.getVertexDefRecordDetail();
     private static final IRecordTypeDetail VERTEX_RECORD_DETAIL = GraphixRecordDetailProvider.getVertexRecordDetail();
     private static final IRecordTypeDetail GRAPH_RECORD_DETAIL = GraphixRecordDetailProvider.getGraphRecordDetail();
 
@@ -131,39 +128,32 @@
             IAObject labelNameObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_LABEL);
             ElementLabel vertexLabel = new ElementLabel(((AString) labelNameObj).getStringValue());
 
-            // Read in our vertex definitions.
-            IAObject definitionsObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_DEFINITIONS);
-            IACursor definitionsCursor = ((AOrderedList) definitionsObj).getCursor();
-            while (definitionsCursor.next()) {
-                ARecord definition = (ARecord) definitionsCursor.get();
+            // Read in the primary key fields.
+            List<List<String>> primaryKeyFields = new ArrayList<>();
+            IAObject primaryKeyObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_PRIMARY_KEY);
+            IACursor primaryKeyCursor = ((AOrderedList) primaryKeyObj).getCursor();
+            while (primaryKeyCursor.next()) {
+                IACursor nameCursor = ((AOrderedList) primaryKeyCursor.get()).getCursor();
+                primaryKeyFields.add(readNameList(nameCursor));
+            }
 
-                // Read in the primary key fields.
-                List<List<String>> primaryKeyFields = new ArrayList<>();
-                IAObject primaryKeyObj = VDEF_RECORD_DETAIL.getObjectForField(definition, FIELD_NAME_PRIMARY_KEY);
-                IACursor primaryKeyCursor = ((AOrderedList) primaryKeyObj).getCursor();
-                while (primaryKeyCursor.next()) {
-                    IACursor nameCursor = ((AOrderedList) primaryKeyCursor.get()).getCursor();
-                    primaryKeyFields.add(readNameList(nameCursor));
-                }
+            // Read in the definition body.
+            IAObject bodyObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_BODY);
+            String definitionBody = ((AString) bodyObj).getStringValue();
 
-                // Read in the definition body.
-                IAObject bodyObj = VDEF_RECORD_DETAIL.getObjectForField(definition, FIELD_NAME_BODY);
-                String definitionBody = ((AString) bodyObj).getStringValue();
+            // Read in the vertex definition, and perform validation of the metadata record.
+            schemaBuilder.addVertex(vertexLabel, primaryKeyFields, definitionBody);
+            switch (schemaBuilder.getLastError()) {
+                case NO_ERROR:
+                    break;
 
-                // Read in the vertex definition, and perform validation of the metadata record.
-                schemaBuilder.addVertex(vertexLabel, primaryKeyFields, definitionBody);
-                switch (schemaBuilder.getLastError()) {
-                    case NO_ERROR:
-                        break;
+                case VERTEX_LABEL_CONFLICT:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR,
+                            "Conflicting vertex label found: " + vertexLabel);
 
-                    case CONFLICTING_PRIMARY_KEY:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Conflicting primary keys for vertices with label " + vertexLabel);
-
-                    default:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Constructor vertex was not returned, but the error is not a conflicting primary key!");
-                }
+                default:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR,
+                            "Constructor vertex was not returned, but the error is not a conflicting vertex label!");
             }
         }
 
@@ -185,59 +175,49 @@
             IAObject sourceLabelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_SOURCE_LABEL);
             ElementLabel sourceLabel = new ElementLabel(((AString) sourceLabelNameObj).getStringValue());
 
-            // Read in our edge definitions.
-            IAObject definitionsObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_DEFINITIONS);
-            IACursor definitionsCursor = ((AOrderedList) definitionsObj).getCursor();
-            while (definitionsCursor.next()) {
-                ARecord definition = (ARecord) definitionsCursor.get();
+            // Read in the source key fields.
+            List<List<String>> sourceKeyFields = new ArrayList<>();
+            IAObject sourceKeyObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_SOURCE_KEY);
+            IACursor sourceKeyCursor = ((AOrderedList) sourceKeyObj).getCursor();
+            while (sourceKeyCursor.next()) {
+                IACursor nameCursor = ((AOrderedList) sourceKeyCursor.get()).getCursor();
+                sourceKeyFields.add(readNameList(nameCursor));
+            }
 
-                // Read in the source key fields.
-                List<List<String>> sourceKeyFields = new ArrayList<>();
-                IAObject sourceKeyObj = EDEF_RECORD_DETAIL.getObjectForField(definition, FIELD_NAME_SOURCE_KEY);
-                IACursor sourceKeyCursor = ((AOrderedList) sourceKeyObj).getCursor();
-                while (sourceKeyCursor.next()) {
-                    IACursor nameCursor = ((AOrderedList) sourceKeyCursor.get()).getCursor();
-                    sourceKeyFields.add(readNameList(nameCursor));
-                }
+            // Read in the destination key fields (this is common to all edge definition records).
+            List<List<String>> destinationKeyFields = new ArrayList<>();
+            IAObject destinationKeyObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_DESTINATION_KEY);
+            IACursor destinationKeyCursor = ((AOrderedList) destinationKeyObj).getCursor();
+            while (destinationKeyCursor.next()) {
+                IACursor nameCursor = ((AOrderedList) destinationKeyCursor.get()).getCursor();
+                destinationKeyFields.add(readNameList(nameCursor));
+            }
 
-                // Read in the destination key fields (this is common to all edge definition records).
-                List<List<String>> destinationKeyFields = new ArrayList<>();
-                IAObject destinationKeyObj =
-                        EDEF_RECORD_DETAIL.getObjectForField(definition, FIELD_NAME_DESTINATION_KEY);
-                IACursor destinationKeyCursor = ((AOrderedList) destinationKeyObj).getCursor();
-                while (destinationKeyCursor.next()) {
-                    IACursor nameCursor = ((AOrderedList) destinationKeyCursor.get()).getCursor();
-                    destinationKeyFields.add(readNameList(nameCursor));
-                }
+            // Read in the definition body.
+            IAObject bodyObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_BODY);
+            String definitionBody = ((AString) bodyObj).getStringValue();
 
-                // Read in the definition body.
-                IAObject bodyObj = EDEF_RECORD_DETAIL.getObjectForField(definition, FIELD_NAME_BODY);
-                String definitionBody = ((AString) bodyObj).getStringValue();
+            // Finally, read in the edge definition and perform validation of the metadata record.
+            schemaBuilder.addEdge(edgeLabel, destinationLabel, sourceLabel, destinationKeyFields, sourceKeyFields,
+                    definitionBody);
+            switch (schemaBuilder.getLastError()) {
+                case NO_ERROR:
+                    break;
 
-                // Finally, read in the edge definition and perform validation of the metadata record.
-                schemaBuilder.addEdge(edgeLabel, destinationLabel, sourceLabel, destinationKeyFields, sourceKeyFields,
-                        definitionBody);
-                switch (schemaBuilder.getLastError()) {
-                    case NO_ERROR:
-                        break;
+                case SOURCE_VERTEX_NOT_FOUND:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR,
+                            "Source vertex " + sourceLabel + " not found in the edge " + edgeLabel + ".");
 
-                    case SOURCE_VERTEX_NOT_FOUND:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Source vertex " + sourceLabel + " not found in the edge " + edgeLabel + ".");
+                case DESTINATION_VERTEX_NOT_FOUND:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR,
+                            "Destination vertex " + destinationLabel + " not found in the edge " + edgeLabel + ".");
 
-                    case DESTINATION_VERTEX_NOT_FOUND:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Destination vertex " + destinationLabel + " not found in the edge " + edgeLabel + ".");
+                case EDGE_LABEL_CONFLICT:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR, "Conflicting edge label found: " + edgeLabel);
 
-                    case CONFLICTING_SOURCE_KEY:
-                    case CONFLICTING_DESTINATION_KEY:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Conflicting edge with the same label found: " + edgeLabel);
-
-                    default:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Edge constructor was not returned, and an unexpected error encountered");
-                }
+                default:
+                    throw new AsterixException(ErrorCode.METADATA_ERROR,
+                            "Edge constructor was not returned, and an unexpected error encountered");
             }
         }
 
@@ -317,34 +297,21 @@
         stringSerde.serialize(aString, fieldValue.getDataOutput());
         elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_LABEL), fieldValue);
 
-        // Write the vertex definition(s).
-        defListBuilder.reset((AOrderedListType) VERTEX_RECORD_DETAIL.getTypeForField(FIELD_NAME_DEFINITIONS));
-        for (Vertex.Definition definition : vertex.getDefinitions()) {
-            defRecordBuilder.reset(VDEF_RECORD_DETAIL.getRecordType());
-
-            // Write the primary key fields.
-            fieldValue.reset();
-            innerListBuilder.reset(new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null));
-            for (List<String> keyField : definition.getPrimaryKeyFieldNames()) {
-                writeNameList(keyField, itemValue);
-                innerListBuilder.addItem(itemValue);
-            }
-            innerListBuilder.write(fieldValue.getDataOutput(), true);
-            defRecordBuilder.addField(VDEF_RECORD_DETAIL.getIndexForField(FIELD_NAME_PRIMARY_KEY), fieldValue);
-
-            // Write the definition body.
-            fieldValue.reset();
-            aString.setValue(definition.getDefinition());
-            stringSerde.serialize(aString, fieldValue.getDataOutput());
-            defRecordBuilder.addField(VDEF_RECORD_DETAIL.getIndexForField(FIELD_NAME_BODY), fieldValue);
-
-            fieldValue.reset();
-            defRecordBuilder.write(fieldValue.getDataOutput(), true);
-            defListBuilder.addItem(fieldValue);
-        }
+        // Write the primary key fields.
         fieldValue.reset();
-        defListBuilder.write(fieldValue.getDataOutput(), true);
-        elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEFINITIONS), fieldValue);
+        innerListBuilder.reset(new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null));
+        for (List<String> keyField : vertex.getPrimaryKeyFieldNames()) {
+            writeNameList(keyField, itemValue);
+            innerListBuilder.addItem(itemValue);
+        }
+        innerListBuilder.write(fieldValue.getDataOutput(), true);
+        elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_PRIMARY_KEY), fieldValue);
+
+        // Write the definition body.
+        fieldValue.reset();
+        aString.setValue(vertex.getDefinitionBody());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_BODY), fieldValue);
 
         itemValue.reset();
         elemRecordBuilder.write(itemValue.getDataOutput(), true);
@@ -371,44 +338,31 @@
         stringSerde.serialize(aString, fieldValue.getDataOutput());
         elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_SOURCE_LABEL), fieldValue);
 
-        // Write the edge definition(s).
-        defListBuilder.reset((AOrderedListType) EDGE_RECORD_DETAIL.getTypeForField(FIELD_NAME_DEFINITIONS));
-        for (Edge.Definition definition : edge.getDefinitions()) {
-            defRecordBuilder.reset(EDEF_RECORD_DETAIL.getRecordType());
-
-            // Write the source key fields.
-            fieldValue.reset();
-            innerListBuilder.reset(stringListList);
-            for (List<String> keyField : definition.getSourceKeyFieldNames()) {
-                writeNameList(keyField, itemValue);
-                innerListBuilder.addItem(itemValue);
-            }
-            innerListBuilder.write(fieldValue.getDataOutput(), true);
-            defRecordBuilder.addField(EDEF_RECORD_DETAIL.getIndexForField(FIELD_NAME_SOURCE_KEY), fieldValue);
-
-            // Write the destination key fields.
-            fieldValue.reset();
-            innerListBuilder.reset(stringListList);
-            for (List<String> keyField : definition.getDestinationKeyFieldNames()) {
-                writeNameList(keyField, itemValue);
-                innerListBuilder.addItem(itemValue);
-            }
-            innerListBuilder.write(fieldValue.getDataOutput(), true);
-            defRecordBuilder.addField(EDEF_RECORD_DETAIL.getIndexForField(FIELD_NAME_DESTINATION_KEY), fieldValue);
-
-            // Write the definition body.
-            fieldValue.reset();
-            aString.setValue(definition.getDefinition());
-            stringSerde.serialize(aString, fieldValue.getDataOutput());
-            defRecordBuilder.addField(EDEF_RECORD_DETAIL.getIndexForField(FIELD_NAME_BODY), fieldValue);
-
-            fieldValue.reset();
-            defRecordBuilder.write(fieldValue.getDataOutput(), true);
-            defListBuilder.addItem(fieldValue);
-        }
+        // Write the source key fields.
         fieldValue.reset();
-        defListBuilder.write(fieldValue.getDataOutput(), true);
-        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEFINITIONS), fieldValue);
+        innerListBuilder.reset(stringListList);
+        for (List<String> keyField : edge.getSourceKeyFieldNames()) {
+            writeNameList(keyField, itemValue);
+            innerListBuilder.addItem(itemValue);
+        }
+        innerListBuilder.write(fieldValue.getDataOutput(), true);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_SOURCE_KEY), fieldValue);
+
+        // Write the destination key fields.
+        fieldValue.reset();
+        innerListBuilder.reset(stringListList);
+        for (List<String> keyField : edge.getDestinationKeyFieldNames()) {
+            writeNameList(keyField, itemValue);
+            innerListBuilder.addItem(itemValue);
+        }
+        innerListBuilder.write(fieldValue.getDataOutput(), true);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_DESTINATION_KEY), fieldValue);
+
+        // Write the definition body.
+        fieldValue.reset();
+        aString.setValue(edge.getDefinitionBody());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_BODY), fieldValue);
 
         itemValue.reset();
         elemRecordBuilder.write(itemValue.getDataOutput(), true);
diff --git a/asterix-graphix/src/main/resources/cc.conf b/asterix-graphix/src/main/resources/cc.conf
index 8452471..ae068ca 100644
--- a/asterix-graphix/src/main/resources/cc.conf
+++ b/asterix-graphix/src/main/resources/cc.conf
@@ -15,50 +15,21 @@
 ; specific language governing permissions and limitations
 ; under the License.
 
-[nc/asterix_nc1]
-txn.log.dir=target/tmp/asterix_nc1/txnlog
-core.dump.dir=target/tmp/asterix_nc1/coredump
-iodevices=target/tmp/asterix_nc1/iodevice1,../asterix-server/target/tmp/asterix_nc1/iodevice2
-#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006
-
-[nc/asterix_nc2]
-ncservice.port=9091
-nc.api.port=19005
-txn.log.dir=target/tmp/asterix_nc2/txnlog
-core.dump.dir=target/tmp/asterix_nc2/coredump
-iodevices=target/tmp/asterix_nc2/iodevice1,../asterix-server/target/tmp/asterix_nc2/iodevice2
-#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5007
+[nc/asterix_nc]
+txn.log.dir=target/tmp/asterix_nc/txnlog
+core.dump.dir=target/tmp/asterix_nc/coredump
+iodevices=target/tmp/asterix_nc/iodevice
 
 [nc]
 address=127.0.0.1
 command=asterixnc
-app.class=org.apache.asterix.hyracks.bootstrap.NCApplication
-jvm.args=-Xmx4096m -Dnode.Resolver="org.apache.asterix.external.util.IdentitiyResolverFactory"
-storage.buffercache.pagesize=32KB
-storage.buffercache.size=128MB
-storage.memorycomponent.globalbudget=512MB
-storage.io.scheduler=greedy
-storage.filtered.memorycomponent.max.size=16MB
 
 [cc]
 address=127.0.0.1
-app.class=org.apache.asterix.hyracks.bootstrap.CCApplication
-heartbeat.period=2000
-heartbeat.max.misses=25
 
 [common]
 log.dir=logs/
 log.level=INFO
-compiler.framesize=32KB
-compiler.sortmemory=320KB
-compiler.groupmemory=160KB
-compiler.joinmemory=256KB
-compiler.textsearchmemory=160KB
-compiler.windowmemory=192KB
-compiler.sort.parallel=false
-compiler.internal.sanitycheck=true
-messaging.frame.size=4096
-messaging.frame.count=512
 
 [extension/org.apache.asterix.graphix.extension.GraphixQueryTranslatorExtension]
 enabled=true
diff --git a/asterix-graphix/src/main/resources/lang-extension/lang.txt b/asterix-graphix/src/main/resources/lang-extension/lang.txt
index a1d12e6..5338219 100644
--- a/asterix-graphix/src/main/resources/lang-extension/lang.txt
+++ b/asterix-graphix/src/main/resources/lang-extension/lang.txt
@@ -30,8 +30,9 @@
 import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
 import org.apache.asterix.graphix.lang.optype.MatchType;
 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.GraphElementDecl;
+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.lang.sqlpp.parser.ParseException;
@@ -39,10 +40,10 @@
 import org.apache.asterix.lang.sqlpp.parser.Token;
 
 @new_at_the_class_def
-public GraphElementDecl parseGraphElementBody(GraphElementIdentifier identifier) throws CompilationException {
-    return parseImpl(new ParseFunction<GraphElementDecl>() {
+public GraphElementDeclaration parseGraphElementBody(GraphElementIdentifier identifier) throws CompilationException {
+    return parseImpl(new ParseFunction<GraphElementDeclaration>() {
         @Override
-        public GraphElementDecl parse() throws ParseException {
+        public GraphElementDeclaration parse() throws ParseException {
             DataverseName dataverse = defaultDataverse;
             defaultDataverse = identifier.getGraphIdentifier().getDataverseName();
 
@@ -52,7 +53,7 @@
             removeCurrentScope();
 
             defaultDataverse = dataverse;
-            return new GraphElementDecl(identifier, elementBodyExpr);
+            return new GraphElementDeclaration(identifier, elementBodyExpr);
         }
     });
 }
@@ -153,57 +154,135 @@
   }
 }
 
-@merge
-Statement CreateStatement() throws ParseException:
+@override
+Statement SingleStatement() throws ParseException:
 {
-  // merge area 1
-  before:
-  after:
+  Statement stmt = null;
 }
 {
   (
-    // merge area 2
+    stmt = DataverseDeclaration()
+    | stmt = DeclareStatement()
+    | stmt = CreateStatement()
+    | stmt = LoadStatement()
+    | stmt = DropStatement()
+    | stmt = WriteStatement()
+    | stmt = SetStatement()
+    | stmt = InsertStatement()
+    | stmt = DeleteStatement()
+    | stmt = UpdateStatement()
+    | stmt = UpsertStatement()
+    | stmt = ConnectionStatement()
+    | stmt = CompactStatement()
+    | stmt = Query()
+    | stmt = RefreshExternalDatasetStatement()
+  )
+  {
+    return stmt;
+  }
+}
+
+@new
+Statement DeclareStatement() throws ParseException:
+{
+  Token startToken = null;
+  Statement stmt = null;
+}
+{
+  <DECLARE> { startToken = token; }
+  (
+    stmt = FunctionDecl(startToken)
+    | stmt = GraphDecl(startToken)
+  )
+  {
+    return stmt;
+  }
+}
+
+// Note: this is the same as FunctionDeclaration in the main grammar with the <DECLARE> token removed.
+@new
+FunctionDecl FunctionDecl(Token startToken) throws ParseException:
+{
+  String functionName;
+  Pair<Integer, List<Pair<VarIdentifier,TypeExpression>>> paramsWithArity = null;
+  Expression funcBody;
+  createNewScope();
+}
+{
+  <FUNCTION>
+  functionName = Identifier()
+  paramsWithArity = FunctionParameters()
+  <LEFTBRACE>
+  funcBody = FunctionBody()
+  <RIGHTBRACE>
+  {
+    int arity = paramsWithArity.first;
+    List<Pair<VarIdentifier,TypeExpression>> paramList = paramsWithArity.second;
+    FunctionSignature signature = new FunctionSignature(defaultDataverse, functionName, arity);
+    getCurrentScope().addFunctionDescriptor(signature, false);
+    ensureNoTypeDeclsInFunction(functionName, paramList, null, startToken);
+    List<VarIdentifier> params = new ArrayList<VarIdentifier>(paramList.size());
+    for (Pair<VarIdentifier,TypeExpression> p: paramList) {
+        params.add(p.getFirst());
+    }
+    FunctionDecl stmt = new FunctionDecl(signature, params, funcBody, false);
+    removeCurrentScope();
+    return addSourceLocation(stmt, startToken);
+  }
+}
+
+@merge
+Statement CreateStatement() throws ParseException:
+{
+}
+{
+  (
     before:
     after:    | stmt = CreateGraphStatement(startToken, false)
   )
   {
-    // merge area 3
   }
 }
 
 @merge
 Statement CreateOrReplaceStatement(Token startStmtToken) throws ParseException:
 {
-  // merge area 1
-  before:
-  after:
 }
 {
   (
-    // merge area 2
     before:
     after:    | stmt = CreateGraphStatement(startStmtToken, true)
   )
   {
-    // merge area 3
   }
 }
 
 @merge
 Statement DropStatement() throws ParseException:
 {
-  // merge area 1
-  before:
-  after:
 }
 {
   (
-    // merge area 2
     before:
     after:    | stmt = DropGraphStatement(startToken)
   )
   {
-    // merge area 3
+  }
+}
+
+@new
+DeclareGraphStatement GraphDecl(Token startStmtToken) throws ParseException:
+{
+  GraphConstructor graphConstructor = null;
+  String graphName = null;
+}
+{
+  <GRAPH>
+  graphName = Identifier()
+  <AS> graphConstructor = GraphConstructor(token)
+  {
+    DeclareGraphStatement stmt = new DeclareGraphStatement(graphName, graphConstructor);
+    return addSourceLocation(stmt, startStmtToken);
   }
 }
 
diff --git a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixExecutionTest.java b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixExecutionTest.java
index 9c83ed5..5c8ac96 100644
--- a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixExecutionTest.java
+++ b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixExecutionTest.java
@@ -38,7 +38,7 @@
 public class GraphixExecutionTest {
     protected static final String PATH_ACTUAL = FileUtil.joinPath("target", "rttest");
     protected static final String PATH_BASE = FileUtil.joinPath("src", "test", "resources", "runtimets");
-    protected static final String FILE_TEST_CONFIG = FileUtil.joinPath("src", "main", "resources", "cc.conf");
+    protected static final String FILE_TEST_CONFIG = FileUtil.joinPath("src", "test", "resources", "cc.conf");
     private static final String TEST_SUITE_FILE = "testsuite.xml";
     private static final String ONLY_SUITE_FILE = "only.xml";
 
diff --git a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixIntegrationUtil.java b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixIntegrationUtil.java
index 0bafd46..63e94c6 100644
--- a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixIntegrationUtil.java
+++ b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixIntegrationUtil.java
@@ -23,7 +23,7 @@
 @RunWith(Parameterized.class)
 public class GraphixIntegrationUtil extends AsterixHyracksIntegrationUtil {
     private static final String FILE_DEFAULT_CONF =
-            FileUtil.joinPath("asterixdb", "asterix-opt", "asterix-graphix", "src", "main", "resources", "cc.conf");
+            FileUtil.joinPath("asterixdb", "asterix-opt", "asterix-graphix", "src", "test", "resources", "cc.conf");
 
     public static void main(String[] args) {
         TestUtils.redirectLoggingToConsole();
diff --git a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixMetadataTest.java b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixMetadataTest.java
index 31d8a8e..ca8cc01 100644
--- a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixMetadataTest.java
+++ b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/GraphixMetadataTest.java
@@ -34,7 +34,7 @@
 public class GraphixMetadataTest {
     private static final String PATH_ACTUAL = FileUtil.joinPath("target", "mdtest");
     private static final String PATH_BASE = FileUtil.joinPath("src", "test", "resources", "metadatats");
-    private static final String FILE_TEST_CONFIG = FileUtil.joinPath("src", "main", "resources", "cc.conf");
+    private static final String FILE_TEST_CONFIG = FileUtil.joinPath("src", "test", "resources", "cc.conf");
 
     private static final TestExecutor testExecutor = new TestExecutor();
     private static final GraphixIntegrationUtil integrationUtil = new GraphixIntegrationUtil();
diff --git a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/SqlppExecutionTest.java b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/SqlppExecutionTest.java
index d9e8ead..002a530 100644
--- a/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/SqlppExecutionTest.java
+++ b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/SqlppExecutionTest.java
@@ -44,7 +44,7 @@
 
     // We should be in the "asterix-app" directory.
     protected static final String FILE_TEST_CONFIG =
-            FileUtil.joinPath("..", "asterix-opt", "asterix-graphix", "src", "main", "resources", "cc.conf");
+            FileUtil.joinPath("..", "asterix-opt", "asterix-graphix", "src", "test", "resources", "cc.conf");
 
     private static final GraphixIntegrationUtil integrationUtil = new GraphixIntegrationUtil();
     private static final TestExecutor testExecutor = new TestExecutor();
diff --git a/asterix-graphix/src/test/resources/cc.conf b/asterix-graphix/src/test/resources/cc.conf
new file mode 100644
index 0000000..8452471
--- /dev/null
+++ b/asterix-graphix/src/test/resources/cc.conf
@@ -0,0 +1,68 @@
+; 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.
+
+[nc/asterix_nc1]
+txn.log.dir=target/tmp/asterix_nc1/txnlog
+core.dump.dir=target/tmp/asterix_nc1/coredump
+iodevices=target/tmp/asterix_nc1/iodevice1,../asterix-server/target/tmp/asterix_nc1/iodevice2
+#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5006
+
+[nc/asterix_nc2]
+ncservice.port=9091
+nc.api.port=19005
+txn.log.dir=target/tmp/asterix_nc2/txnlog
+core.dump.dir=target/tmp/asterix_nc2/coredump
+iodevices=target/tmp/asterix_nc2/iodevice1,../asterix-server/target/tmp/asterix_nc2/iodevice2
+#jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5007
+
+[nc]
+address=127.0.0.1
+command=asterixnc
+app.class=org.apache.asterix.hyracks.bootstrap.NCApplication
+jvm.args=-Xmx4096m -Dnode.Resolver="org.apache.asterix.external.util.IdentitiyResolverFactory"
+storage.buffercache.pagesize=32KB
+storage.buffercache.size=128MB
+storage.memorycomponent.globalbudget=512MB
+storage.io.scheduler=greedy
+storage.filtered.memorycomponent.max.size=16MB
+
+[cc]
+address=127.0.0.1
+app.class=org.apache.asterix.hyracks.bootstrap.CCApplication
+heartbeat.period=2000
+heartbeat.max.misses=25
+
+[common]
+log.dir=logs/
+log.level=INFO
+compiler.framesize=32KB
+compiler.sortmemory=320KB
+compiler.groupmemory=160KB
+compiler.joinmemory=256KB
+compiler.textsearchmemory=160KB
+compiler.windowmemory=192KB
+compiler.sort.parallel=false
+compiler.internal.sanitycheck=true
+messaging.frame.size=4096
+messaging.frame.count=512
+
+[extension/org.apache.asterix.graphix.extension.GraphixQueryTranslatorExtension]
+enabled=true
+[extension/org.apache.asterix.graphix.extension.GraphixLangExtension]
+enabled=true
+[extension/org.apache.asterix.graphix.extension.GraphixMetadataExtension]
+enabled=true
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
index db8589e..a36f331 100644
--- a/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
@@ -80,11 +80,6 @@
                   AS ( FROM    Yelp_B.Users U
                        UNNEST  U.friends F
                        SELECT  U.user_id, F AS friend ),
-EDGE              (:User)-[:FRIENDS_WITH]->(:User)
-                  SOURCE KEY (user_id)
-                  DESTINATION KEY (friend)
-                  AS ( FROM    Yelp_B.Friends F
-                       SELECT  F.* ),
 EDGE              (:Review)-[:MADE_BY]->(:User)
                   SOURCE KEY (review_id)
                   DESTINATION KEY (user_id)
diff --git a/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
index 0835454..993abe4 100644
--- a/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
@@ -35,11 +35,6 @@
                           AS ( FROM    Yelp_B.Users U
                                UNNEST  U.friends F
                                SELECT  U.user_id, F AS friend ),
-EDGE                      (:User)-[:FRIENDS_WITH]->(:User)
-                          SOURCE KEY (user_id)
-                          DESTINATION KEY (friend)
-                          AS ( FROM    Yelp_B.Friends F
-                               SELECT  F.* ),
 EDGE                      (:Review)-[:MADE_BY]->(:User)
                           SOURCE KEY (review_id)
                           DESTINATION KEY (user_id)
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.2.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.2.adm
index 2e8cfe1..8440201 100644
--- a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.2.adm
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.2.adm
@@ -1,7 +1,6 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                       SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                       SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                       SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Friends", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                       SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Users", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                       SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Yelpers", "DependentKind": "SYNONYM" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
\ No newline at end of file
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ], "DependentDataverse": "Yelp_B", "DependentName": "Users", "DependentKind": "DATASET" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.user_id" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  R.review_id, R.business_id" } ], "DependentDataverse": "Yelp_B", "DependentName": "Yelpers", "DependentKind": "SYNONYM" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.4.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.4.adm
index 2f07e94..5d218fe 100644
--- a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.4.adm
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.4.adm
@@ -1,2 +1,2 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
\ No newline at end of file
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B" } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.6.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.6.adm
index f761b45..1c70d53 100644
--- a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.6.adm
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.6.adm
@@ -1,5 +1,4 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                               SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                               SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                               SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Friends", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                               SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Users", "DependentKind": "DATASET" }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "Definitions": [ { "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" } ] }, { "Label": "Review", "Definitions": [ { "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "Business", "Definitions": [ { "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "Definitions": [ { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Friends F\n                               SELECT  F.*" } ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "Definitions": [ { "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ] } ], "DependentDataverse": "Yelp_B", "DependentName": "Yelpers", "DependentKind": "SYNONYM" }
\ No newline at end of file
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "RelevantBusinesses", "DependentKind": "FUNCTION" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ], "DependentDataverse": "Yelp_A", "DependentName": "Reviews", "DependentKind": "DATASET" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ], "DependentDataverse": "Yelp_B", "DependentName": "Users", "DependentKind": "DATASET" }
+{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Body": "Yelp_B.Yelpers" }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B" } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "SourceKey": [ [ "user_id" ] ], "DestinationKey": [ [ "friend" ] ], "Body": "FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend" }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "SourceKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "Body": "FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R" } ], "DependentDataverse": "Yelp_B", "DependentName": "Yelpers", "DependentKind": "SYNONYM" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp
index eb0fa47..72cd219 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Verify that vertices w/ the same label cannot have conflicting primary keys.
+// Verify that no two vertices can have the same label.
 
 DROP DATAVERSE    TestDataverse IF EXISTS;
 CREATE DATAVERSE  TestDataverse;
@@ -34,6 +34,6 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 VERTEX            (:Vertex1)
-                  PRIMARY KEY (_other_id)
+                  PRIMARY KEY (_id)
                   AS ( FROM GenericDataset
                        SELECT VALUE { "_other_id": _other_id } );
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
index b37cb34..78851df 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
@@ -17,6 +17,8 @@
  * under the License.
  */
 
+-- param max-warnings:string=2
+
 // There are two dangling vertices of different labels, and zero edges.
 FROM GRAPH  Yelp.YelpGraph
 MATCH       (u:User), (r:Review)
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.3.query.sqlpp
index ee62ea8..6b4ee2e 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.3.query.sqlpp
@@ -28,10 +28,9 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)<-[fw:FRIENDS_WITH{2,2}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
+UNNEST      fw.Vertices AS pathVertex
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_hops
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            pathVertex.user_id AS path_user_id,
+            LEN(fw.Edges) AS fw_hops
+ORDER BY    u1, pathVertex, u2;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.4.query.sqlpp
index bfef02f..a128b19 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.4.query.sqlpp
@@ -28,10 +28,9 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)<-[fw:FRIENDS_WITH{3,3}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
+UNNEST      fw.Vertices AS pathVertex
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_hops
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            pathVertex.user_id AS path_user_id,
+            LEN(fw.Edges) AS fw_hops
+ORDER BY    u1, pathVertex, u2;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.5.query.sqlpp
index e12bf13..16060ca 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.5.query.sqlpp
@@ -29,15 +29,9 @@
                                 F.friend AS friend )
 MATCH       (u2:User)<-[fw1:FRIENDS_WITH{2,2}]-(u1:User),
             (u3:User)<-[fw2:FRIENDS_WITH{2,2}]-(u2)
-UNNEST      fw1 AS fw1EdgeRecord
-UNNEST      fw2 AS fw2EdgeRecord
-LET         fw1_left_vertex = VERTEX_KEY(fw1EdgeRecord.LeftVertex),
-            fw2_left_vertex = VERTEX_KEY(fw2EdgeRecord.LeftVertex)
 SELECT      u1.user_id AS u1_user_id,
-            fw1_left_vertex,
             u2.user_id AS u2_user_id,
-            fw2_left_vertex,
             u3.user_id AS u3_user_id,
-            HOP_COUNT(fw1) AS fw1_hops,
-            HOP_COUNT(fw2) AS fw2_hops
-ORDER BY    u1, fw1_left_vertex, u2, fw2_left_vertex, u3;
\ No newline at end of file
+            LEN(fw1.Edges) AS fw1_hops,
+            LEN(fw2.Edges) AS fw2_hops
+ORDER BY    u1, u2, u3;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.6.query.sqlpp
index ec08830..7630241 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.6.query.sqlpp
@@ -28,10 +28,7 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)<-[fw:FRIENDS_WITH{1,1}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_hops
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            LEN(fw.Edges) AS fw_hops
+ORDER BY    u1, u2;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.8.query.sqlpp
index fcedada..56aeb27 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.8.query.sqlpp
@@ -28,10 +28,13 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)-[fw:FRIENDS_WITH{2,2}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
+LET         fwVertices = ( FROM    fw.Vertices FW
+                           SELECT  VALUE FW.user_id ),
+            fwEdges =    ( FROM    fw.Edges FW
+                           SELECT  VALUE FW.friend )
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_hops
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            fwVertices,
+            fwEdges,
+            LEN(fwEdges) AS fw_hops
+ORDER BY    u1, u2, fwVertices, fwEdges;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
index 35463a2..71e441c 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
@@ -17,22 +17,14 @@
  * under the License.
  */
 
-// Test each simple vertex function (label, key, properties, and detail).
+// Test each simple vertex function (label and detail).
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
             VERTEX (:Review)
                    PRIMARY KEY (review_id)
-                   AS Yelp.Reviews,
-            EDGE   (:User)<-[:MADE_BY]-(:Review)
-                   SOURCE KEY (review_id)
-                   DESTINATION KEY (user_id)
-                   AS ( FROM    Yelp.Reviews R
-                        SELECT  R.review_id,
-                                R.user_id )
+                   AS Yelp.Reviews
 MATCH       (n)
-SELECT      VERTEX_KEY(n) AS vertexKey,
-            LABEL(n) AS vertexLabel,
-            VERTEX_PROPERTIES(n) AS vertexProperties,
+SELECT      LABEL(n) AS vertexLabel,
             VERTEX_DETAIL(n) AS vertexDetail
 ORDER BY    n;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
index 20f08c2..0283334 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Test each simple edge function (direction, label, keys, detail, properties).
+// Test each simple edge function (direction, label, and detail).
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
@@ -37,14 +37,13 @@
                         SELECT  R.review_id,
                                 R.user_id )
 MATCH       (n1)-[e]-(n2)
+LET         sourceVertex = SOURCE_VERTEX(e),
+            destVertex = DEST_VERTEX(e)
 SELECT      DIRECTION(e) AS direction,
-            SOURCE_KEY(e) AS sourceKeyValue,
-            DEST_KEY(e) AS destKeyValue,
-            LEFT_TO_RIGHT_IF(e, n1, n2) AS sourceVertex,
-            RIGHT_TO_LEFT_IF(e, n1, n2) AS destVertex,
-            EDGE_DETAIL(e) AS edgeDetail,
-            EDGE_PROPERTIES(e) AS edgeProperties,
             LABEL(e) AS edgeLabel,
             LABEL(n1) AS n1Label,
-            LABEL(n2) AS n2Label
-ORDER BY    n1, e, n2;
\ No newline at end of file
+            LABEL(n2) AS n2Label,
+            LABEL(sourceVertex) AS sourceVertexLabel,
+            LABEL(destVertex) AS destVertexLabel,
+            EDGE_DETAIL(e) AS edgeDetail
+ORDER BY    direction, edgeLabel, n1Label, n2Label, edgeDetail;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
index 3595efb..a427758 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
@@ -20,34 +20,17 @@
 // Test each path function (edges, hop-count, labels, vertices).
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
-                   AS ( FROM      Yelp.Users U
-                        SELECT    VALUE U
-                        ORDER BY  U.user_id
-                        LIMIT     2 ),
-            VERTEX (:Review)
-                   PRIMARY KEY (review_id)
-                   AS ( FROM      Yelp.Reviews R
-                        SELECT    VALUE R
-                        ORDER BY  R.review_id
-                        LIMIT     2 ),
+                   AS Yelp.Users,
             EDGE   (:User)-[:FRIENDS_WITH]->(:User)
                    SOURCE KEY (user_id)
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
                         SELECT  F.user_id AS user_id,
-                                F.friend AS friend ),
-            EDGE   (:User)<-[:MADE_BY]-(:Review)
-                   SOURCE KEY (review_id)
-                   DESTINATION KEY (user_id)
-                   AS ( FROM    Yelp.Reviews R
-                        SELECT  R.review_id,
-                                R.user_id )
-MATCH       (n1)-[e:{1,1}]-(n2)-(n3) AS p
+                                F.friend AS friend )
+MATCH       (n1)-[e:{1,2}]->(n2)->(n3) AS p
 SELECT      EDGES(p) AS pEdges,
             EDGES(e) AS eEdges,
             HOP_COUNT(p) AS pHopCount,
-            LABELS(p) AS pLabels,
-            LABELS(e) AS eLabels,
             VERTICES(p) AS pVertices,
             VERTICES(e) AS eVertices
 ORDER BY    n1, e, n2, n3;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
index 3d6e9a9..30de634 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
@@ -40,8 +40,7 @@
                    AS ( FROM    Yelp.Reviews R
                         SELECT  R.review_id, R.user_id )
 MATCH       (u:User)-[e:{2,2}]->(n) // (u:User)-[]->()-[]->(n)
-UNNEST      e AS edgeRecord
-LET         ee = edgeRecord.`Edge`
+UNNEST      PATH_EDGES(e) AS ee
 SELECT      DISTINCT LABEL(ee) AS e_label,
                      LABEL(n) AS n_label,
-                     LEN(e) AS e_length;
+                     PATH_HOP_COUNT(e) AS e_length;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp
index e85ca82..9b027ce 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp
@@ -33,53 +33,3 @@
   { "user_id": 2, "friend": 3, "group_id": 3 },
   { "user_id": 2, "friend": null, "group_id": 3 }
 ];
-
-INSERT INTO       Users [
-  { "user_id": 1, "group_id": 4 },
-  { "user_id": 2, "group_id": 4 },
-  { "user_id": 3, "group_id": 4 }
-];
-INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2, "group_id": 4 },
-  { "user_id": 2, "friend": 3, "group_id": 4 }
-];
-INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 2, "group_id": 4 }
-];
-
-INSERT INTO       Users [
-  { "user_id": 1, "group_id": 5 },
-  { "user_id": 2, "group_id": 5 },
-  { "user_id": 3, "group_id": 5 },
-  { "user_id": 4, "group_id": 5 },
-  { "user_id": 5, "group_id": 5 }
-];
-INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2, "group_id": 5 },
-  { "user_id": 3, "friend": 1, "group_id": 5 },
-  { "user_id": 4, "friend": 1, "group_id": 5 },
-  { "user_id": 1, "friend": 4, "group_id": 5 }
-];
-INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 3, "group_id": 5 },
-  { "review_id": "B", "user_id": 4, "group_id": 5 },
-  { "review_id": "C", "user_id": 5, "group_id": 5 }
-];
-
-INSERT INTO       Users [
-  { "user_id": 1, "group_id": 6 },
-  { "user_id": 2, "group_id": 6 },
-  { "user_id": 3, "group_id": 6 },
-  { "user_id": 4, "group_id": 6 },
-  { "user_id": 5, "group_id": 6 }
-];
-INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2, "group_id": 6 },
-  { "user_id": 1, "friend": 3, "group_id": 6 },
-  { "user_id": 3, "friend": 4, "group_id": 6 },
-  { "user_id": 3, "friend": 5, "group_id": 6 }
-];
-INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 2, "group_id": 6 },
-  { "review_id": "B", "user_id": 3, "group_id": 6 }
-];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.update.sqlpp
similarity index 69%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.update.sqlpp
index 24fdd88..d1fac44 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.update.sqlpp
@@ -19,24 +19,19 @@
 
 USE               Yelp;
 
+DELETE FROM       Users;
+DELETE FROM       Reviews;
+DELETE FROM       Friends;
+
 INSERT INTO       Users [
-  { "user_id": 1 },
-  { "user_id": 2 },
-  { "user_id": 3 },
-  { "user_id": 4 }
+  { "user_id": 1, "group_id": 4 },
+  { "user_id": 2, "group_id": 4 },
+  { "user_id": 3, "group_id": 4 }
 ];
-
 INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2 },
-  { "user_id": 4, "friend": 2 },
-  { "user_id": 3, "friend": 2 }
+  { "user_id": 1, "friend": 2, "group_id": 4 },
+  { "user_id": 2, "friend": 3, "group_id": 4 }
 ];
-
 INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 1 },
-  { "review_id": "B", "user_id": 1 },
-  { "review_id": "C", "user_id": 2 },
-  { "review_id": "D", "user_id": 3 },
-  { "review_id": "E", "user_id": 4 },
-  { "review_id": "F", "user_id": 5 }
+  { "review_id": "A", "user_id": 2, "group_id": 4 }
 ];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.5.query.sqlpp
index 3f00813..f4fe1cc 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.5.query.sqlpp
@@ -17,40 +17,36 @@
  * under the License.
  */
 
--- LEFT-MATCH query, with destination of the second LEFT-MATCH edge acting as connecting vertex.
+-- LEFT-MATCH query, with destination of the LEFT-MATCH edge acting as connecting vertex.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
-                        WHERE   U.group_id = 5
+                        WHERE   U.group_id = 4
                         SELECT  U.user_id ),
             VERTEX (:Review)
                    PRIMARY KEY (review_id)
                    AS ( FROM    Yelp.Reviews R
-                        WHERE   R.group_id = 5
+                        WHERE   R.group_id = 4
                         SELECT  R.review_id ),
             EDGE   (:Review)-[:MADE_BY]->(:User)
                    SOURCE KEY (review_id)
                    DESTINATION KEY (user_id)
                    AS ( FROM    Yelp.Reviews R
-                        WHERE   R.group_id = 5
+                        WHERE   R.group_id = 4
                         SELECT  R.review_id, R.user_id ),
             EDGE   (:User)-[:FRIENDS_WITH]->(:User)
                    SOURCE KEY (user_id)
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
-                        WHERE   F.group_id = 5
+                        WHERE   F.group_id = 4
                         SELECT  F.user_id, F.friend )
-MATCH       (u2:User)<-[fw1:FRIENDS_WITH]-(u1:User)
-LEFT MATCH  (u3:User)<-[mb:MADE_BY]-(r:Review),
-            (u1)<-[fw2:FRIENDS_WITH]-(u3)
+MATCH       (u2:User)<-[fw:FRIENDS_WITH]-(u1:User)
+LEFT MATCH  (u1)<-[mb:MADE_BY]-(r:Review)
 SELECT      u1.user_id AS u1_user_id,
-            fw1.user_id AS fw1_user_id,
-            fw1.friend AS fw1_friend,
+            fw.user_id AS fw_user_id,
+            fw.friend AS fw_friend,
             u2.user_id AS u2_user_id,
-            u3.user_id AS u3_user_id,
             mb.user_id AS mb_user_id,
             mb.review_id AS mb_review_id,
-            r.review_id AS r_review_id,
-            fw2.user_id AS fw2_user_id,
-            fw2.friend AS fw2_friend
-ORDER BY    u1_user_id, u2_user_id, u3_user_id, r_review_id;
\ No newline at end of file
+            r.review_id AS r_review_id
+ORDER BY    u1_user_id, u2_user_id, r_review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.update.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.update.sqlpp
index 24fdd88..e1e1c5f 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.update.sqlpp
@@ -19,24 +19,25 @@
 
 USE               Yelp;
 
+DELETE FROM       Users;
+DELETE FROM       Reviews;
+DELETE FROM       Friends;
+
 INSERT INTO       Users [
-  { "user_id": 1 },
-  { "user_id": 2 },
-  { "user_id": 3 },
-  { "user_id": 4 }
+  { "user_id": 1, "group_id": 5 },
+  { "user_id": 2, "group_id": 5 },
+  { "user_id": 3, "group_id": 5 },
+  { "user_id": 4, "group_id": 5 },
+  { "user_id": 5, "group_id": 5 }
 ];
-
 INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2 },
-  { "user_id": 4, "friend": 2 },
-  { "user_id": 3, "friend": 2 }
+  { "user_id": 1, "friend": 2, "group_id": 5 },
+  { "user_id": 3, "friend": 1, "group_id": 5 },
+  { "user_id": 4, "friend": 1, "group_id": 5 },
+  { "user_id": 1, "friend": 4, "group_id": 5 }
 ];
-
 INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 1 },
-  { "review_id": "B", "user_id": 1 },
-  { "review_id": "C", "user_id": 2 },
-  { "review_id": "D", "user_id": 3 },
-  { "review_id": "E", "user_id": 4 },
-  { "review_id": "F", "user_id": 5 }
+  { "review_id": "A", "user_id": 3, "group_id": 5 },
+  { "review_id": "B", "user_id": 4, "group_id": 5 },
+  { "review_id": "C", "user_id": 5, "group_id": 5 }
 ];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.7.query.sqlpp
similarity index 71%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.7.query.sqlpp
index f4fe1cc..3f00813 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.7.query.sqlpp
@@ -17,36 +17,40 @@
  * under the License.
  */
 
--- LEFT-MATCH query, with destination of the LEFT-MATCH edge acting as connecting vertex.
+-- LEFT-MATCH query, with destination of the second LEFT-MATCH edge acting as connecting vertex.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS ( FROM    Yelp.Users U
-                        WHERE   U.group_id = 4
+                        WHERE   U.group_id = 5
                         SELECT  U.user_id ),
             VERTEX (:Review)
                    PRIMARY KEY (review_id)
                    AS ( FROM    Yelp.Reviews R
-                        WHERE   R.group_id = 4
+                        WHERE   R.group_id = 5
                         SELECT  R.review_id ),
             EDGE   (:Review)-[:MADE_BY]->(:User)
                    SOURCE KEY (review_id)
                    DESTINATION KEY (user_id)
                    AS ( FROM    Yelp.Reviews R
-                        WHERE   R.group_id = 4
+                        WHERE   R.group_id = 5
                         SELECT  R.review_id, R.user_id ),
             EDGE   (:User)-[:FRIENDS_WITH]->(:User)
                    SOURCE KEY (user_id)
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
-                        WHERE   F.group_id = 4
+                        WHERE   F.group_id = 5
                         SELECT  F.user_id, F.friend )
-MATCH       (u2:User)<-[fw:FRIENDS_WITH]-(u1:User)
-LEFT MATCH  (u1)<-[mb:MADE_BY]-(r:Review)
+MATCH       (u2:User)<-[fw1:FRIENDS_WITH]-(u1:User)
+LEFT MATCH  (u3:User)<-[mb:MADE_BY]-(r:Review),
+            (u1)<-[fw2:FRIENDS_WITH]-(u3)
 SELECT      u1.user_id AS u1_user_id,
-            fw.user_id AS fw_user_id,
-            fw.friend AS fw_friend,
+            fw1.user_id AS fw1_user_id,
+            fw1.friend AS fw1_friend,
             u2.user_id AS u2_user_id,
+            u3.user_id AS u3_user_id,
             mb.user_id AS mb_user_id,
             mb.review_id AS mb_review_id,
-            r.review_id AS r_review_id
-ORDER BY    u1_user_id, u2_user_id, r_review_id;
\ No newline at end of file
+            r.review_id AS r_review_id,
+            fw2.user_id AS fw2_user_id,
+            fw2.friend AS fw2_friend
+ORDER BY    u1_user_id, u2_user_id, u3_user_id, r_review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.8.update.sqlpp
similarity index 62%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.8.update.sqlpp
index 24fdd88..78d42de 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.8.update.sqlpp
@@ -19,24 +19,24 @@
 
 USE               Yelp;
 
+DELETE FROM       Users;
+DELETE FROM       Reviews;
+DELETE FROM       Friends;
+
 INSERT INTO       Users [
-  { "user_id": 1 },
-  { "user_id": 2 },
-  { "user_id": 3 },
-  { "user_id": 4 }
+  { "user_id": 1, "group_id": 6 },
+  { "user_id": 2, "group_id": 6 },
+  { "user_id": 3, "group_id": 6 },
+  { "user_id": 4, "group_id": 6 },
+  { "user_id": 5, "group_id": 6 }
 ];
-
 INSERT INTO       Friends [
-  { "user_id": 1, "friend": 2 },
-  { "user_id": 4, "friend": 2 },
-  { "user_id": 3, "friend": 2 }
+  { "user_id": 1, "friend": 2, "group_id": 6 },
+  { "user_id": 1, "friend": 3, "group_id": 6 },
+  { "user_id": 3, "friend": 4, "group_id": 6 },
+  { "user_id": 3, "friend": 5, "group_id": 6 }
 ];
-
 INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 1 },
-  { "review_id": "B", "user_id": 1 },
-  { "review_id": "C", "user_id": 2 },
-  { "review_id": "D", "user_id": 3 },
-  { "review_id": "E", "user_id": 4 },
-  { "review_id": "F", "user_id": 5 }
+  { "review_id": "A", "user_id": 2, "group_id": 6 },
+  { "review_id": "B", "user_id": 3, "group_id": 6 }
 ];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.9.query.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.query.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.9.query.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp
deleted file mode 100644
index 96ec87c..0000000
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp
+++ /dev/null
@@ -1,31 +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.
- */
-
-DROP DATAVERSE    Yelp IF EXISTS;
-CREATE DATAVERSE  Yelp;
-USE               Yelp;
-
-CREATE TYPE       GenericType
-AS                { _id: uuid };
-CREATE DATASET    Friends (GenericType)
-PRIMARY KEY       _id AUTOGENERATED;
-CREATE DATASET    Reviews (GenericType)
-PRIMARY KEY       _id AUTOGENERATED;
-CREATE DATASET    Users (GenericType)
-PRIMARY KEY       _id AUTOGENERATED;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp
deleted file mode 100644
index 6b394d6..0000000
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp
+++ /dev/null
@@ -1,29 +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.
- */
-
--- param max-warnings:string=1
-
-// We should be able to determine that (u) is of label User.
-SET         `graphix.resolver` "no-resolution";
-FROM GRAPH  VERTEX (:User)
-                   PRIMARY KEY (user_id)
-                   AS Yelp.Users
-MATCH       (u)
-SELECT      u.user_id
-ORDER BY    u.user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp
deleted file mode 100644
index e78aa4b..0000000
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp
+++ /dev/null
@@ -1,34 +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.
- */
-
--- param max-warnings:string=3
-
-// The (n) vertex should assume all defined vertex labels.
-SET         `graphix.resolver` "no-resolution";
-FROM GRAPH  VERTEX (:User)
-                   PRIMARY KEY (user_id)
-                   AS Yelp.Users,
-            VERTEX (:Review)
-                   PRIMARY KEY (review_id)
-                   AS Yelp.Reviews
-MATCH       (n)
-SELECT      n.user_id,
-            n.review_id
-ORDER BY    n.user_id,
-            n.review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.5.query.sqlpp
deleted file mode 100644
index 1d4b4c2..0000000
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.5.query.sqlpp
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
--- param max-warnings:string=3
-
-// The (n) and (m) vertices should assume all defined vertex labels, and (e) should assume all edge labels.
-SET         `graphix.resolver` "no-resolution";
-FROM GRAPH  VERTEX (:User)
-                   PRIMARY KEY (user_id)
-                   AS ( FROM    Yelp.Users U
-                        SELECT  U.user_id ),
-            VERTEX (:Review)
-                   PRIMARY KEY (review_id)
-                   AS ( FROM    Yelp.Reviews R
-                        SELECT  R.review_id ),
-            EDGE   (:Review)-[:MADE_BY]->(:User)
-                   SOURCE KEY (review_id)
-                   DESTINATION KEY (user_id)
-                   AS ( FROM    Yelp.Reviews R
-                        SELECT  R.review_id, R.user_id )
-MATCH       (m)-[e]-(n)
-SELECT      m.user_id AS left_user_id,
-            m.review_id AS left_review_id,
-            n.user_id AS right_user_id,
-            n.review_id AS right_review_id,
-            e.user_id AS e_user_id,
-            e.review_id AS e_review_id
-ORDER BY    left_user_id,
-            left_review_id,
-            right_user_id,
-            right_review_id,
-            e_user_id,
-            e_review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.9.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.9.query.sqlpp
index ea9aeb8..6093a94 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.9.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.9.query.sqlpp
@@ -23,8 +23,8 @@
                     PRIMARY KEY (_id)
                     AS GenericDataset,
             VERTEX  (:Vertex1)
-                    PRIMARY KEY (_other_id)
-                    AS GenericDataset,
+                    PRIMARY KEY (_id)
+                    AS ( FROM GenericDataset SELECT * ),
             EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
                     SOURCE KEY (_id)
                     DESTINATION KEY (_to_id)
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
index e110262..2554edb 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
@@ -22,7 +22,7 @@
                    PRIMARY KEY (user_id)
                    AS Yelp.Users
 MATCH       (u) AS p
-UNNEST      p AS edgeRecord
-SELECT      PATH_HOP_COUNT(p) AS pathLength,
-            edgeRecord
-ORDER BY    u.user_id, VERTEX_KEY(edgeRecord.LeftVertex);
+UNNEST      p.Vertices AS pathVertex
+SELECT      LEN(p.Edges) AS pathLength,
+            pathVertex
+ORDER BY    u.user_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.4.query.sqlpp
index a96f62a..28136cc 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.4.query.sqlpp
@@ -30,10 +30,8 @@
                    AS ( FROM    Yelp.Reviews R
                         SELECT  R.review_id, R.user_id )
 MATCH       (u:User)<-[:MADE_BY]-(r:Review) AS p
-UNNEST      p AS edgeRecord
-SELECT      PATH_HOP_COUNT(p) AS pathLength,
+UNNEST      p.Edges AS edgeRecord
+SELECT      LEN(p.Edges) AS pathLength,
             edgeRecord
 ORDER BY    u.user_id,
-            r.review_d,
-            VERTEX_KEY(edgeRecord.LeftVertex),
-            SOURCE_KEY(edgeRecord.`Edge`);
+            r.review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.5.query.sqlpp
index 13078e0..5fcd088 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.5.query.sqlpp
@@ -28,11 +28,13 @@
                         WHERE   F.friend_group = "A"
                         SELECT  F.user_id, F.friend )
 MATCH       (u)-[e1]-(v)-[e2]-(w) AS p
-UNNEST      p AS edgeRecord
-SELECT      PATH_HOP_COUNT(p) AS pathLength,
-            edgeRecord
-ORDER BY    u.user_id,
-            v.user_id,
-            w.user_id,
-            VERTEX_KEY(edgeRecord.LeftVertex),
-            SOURCE_KEY(edgeRecord.`Edge`);
\ No newline at end of file
+UNNEST      p.Edges AS edgeRecord
+SELECT      LEN(p.Edges) AS pathLength,
+            edgeRecord,
+            u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id
+ORDER BY    u_user_id,
+            v_user_id,
+            w_user_id,
+            edgeRecord.user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
index ffef1a7..595463c 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
@@ -28,10 +28,8 @@
                         WHERE   F.friend_group = "A"
                         SELECT  F.user_id, F.friend )
 MATCH       (u)-[e1:{1,2}]-(v) AS p
-UNNEST      p AS edgeRecord
-SELECT      PATH_HOP_COUNT(p) AS pathLength,
-            edgeRecord
-ORDER BY    u.user_id,
-            v.user_id,
-            VERTEX_KEY(edgeRecord.LeftVertex),
-            SOURCE_KEY(edgeRecord.`Edge`);
\ No newline at end of file
+SELECT      LEN(p.Edges) AS pathLength,
+            u.user_id AS u_user_id,
+            v.user_id AS v_user_id
+ORDER BY    u_user_id,
+            v_user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
index 40e57c3..c0c8d48 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
@@ -33,7 +33,9 @@
                              SELECT    VALUE ( FROM    PATH_VERTICES(gi.p) pv
                                                SELECT  VALUE pv.user_id )
                              ORDER BY  PATH_HOP_COUNT(gi.p) ASC
-                             LIMIT     1 )
+                             LIMIT     1 )[0]
 SELECT      u1.user_id AS u1_user_id,
             u2.user_id AS u2_user_id,
-            shortestPath[0] AS shortestPath;
\ No newline at end of file
+            shortestPath
+ORDER BY    u1_user_id,
+            u2_user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
index 1404a75..62e6a83 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
@@ -39,4 +39,6 @@
               SELECT    VALUE ( FROM    gi.pathVertices AS pv
                                 SELECT  VALUE pv.user_id )
               ORDER BY  gi.pathHopCount ASC
-              LIMIT     1 )[0] AS shortestPath;
\ No newline at end of file
+              LIMIT     1 )[0] AS shortestPath
+ORDER BY    myUser1,
+            myUser2;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.2.update.sqlpp
index 21365f1..d89e52b 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.2.update.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.2.update.sqlpp
@@ -33,10 +33,10 @@
 ];
 
 INSERT INTO       Reviews [
-  { "review_id": "A", "user_id": 1 },
-  { "review_id": "B", "user_id": 1 },
-  { "review_id": "C", "user_id": 2 },
-  { "review_id": "D", "user_id": 3 },
-  { "review_id": "E", "user_id": 4 },
-  { "review_id": "F", "user_id": 5 }
+  { "review_id": "A", "review_user_id": 1 },
+  { "review_id": "B", "review_user_id": 1 },
+  { "review_id": "C", "review_user_id": 2 },
+  { "review_id": "D", "review_user_id": 3 },
+  { "review_id": "E", "review_user_id": 4 },
+  { "review_id": "F", "review_user_id": 5 }
 ];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.4.query.sqlpp
index 9e57a33..79bec1a 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.4.query.sqlpp
@@ -17,29 +17,24 @@
  * under the License.
  */
 
-// Two edge definitions, single vertex definition on both sides.
+// One edge definition, single vertex definition on both sides, with a dangling vertex.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
-            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
-                   SOURCE KEY (user_id)
-                   DESTINATION KEY (friend)
-                   AS ( FROM    Yelp.Users U
-                        UNNEST  U.friends F
-                        SELECT  U.user_id AS user_id,
-                                F AS friend ),
-            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
-                   SOURCE KEY (user_id)
-                   DESTINATION KEY (friend)
-                   AS ( FROM    Yelp.Friends F
-                        SELECT  F.user_id AS user_id,
-                                F.friend AS friend )
-MATCH       (u2:User)<-[fw:FRIENDS_WITH]-(u1:User)
-SELECT      u1.user_id AS u1_user_id,
-            u2.user_id AS u2_user_id,
-            fw.user_id AS fw_user_id,
-            fw.friend AS fw_friend
-ORDER BY    u1_user_id,
-            u2_user_id,
-            fw_user_id,
-            fw_friend;
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (review_user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_user_id, R.review_id )
+MATCH       (u:User)<-[:MADE_BY]-(r:Review), (n)
+SELECT      u.user_id AS u_user_id,
+            r.review_id AS r_review_id,
+            n.user_id AS n_user_id,
+            n.review_id AS n_review_id
+ORDER BY    u_user_id,
+            r_review_id,
+            n_user_id,
+            n_review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.5.query.sqlpp
index ff0d317..ac35dec 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.5.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.5.query.sqlpp
@@ -17,33 +17,21 @@
  * under the License.
  */
 
-// Two edge definitions, one vertex definition on the source and two vertex definitions on the destination.
+// One edge definition, single vertex definition on both sides, undirected.
 FROM GRAPH  VERTEX (:User)
                    PRIMARY KEY (user_id)
                    AS Yelp.Users,
-            VERTEX (:User)
-                   PRIMARY KEY (user_id)
-                   AS ( FROM    Yelp.Friends F
-                        WHERE   F.user_id NOT IN (
-                            FROM    Yelp.Users U
-                            SELECT  VALUE U.user_id
-                        )
-                        SELECT  DISTINCT F.user_id ),
             EDGE   (:User)-[:FRIENDS_WITH]->(:User)
                    SOURCE KEY (user_id)
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Users U
                         UNNEST  U.friends F
                         SELECT  U.user_id AS user_id,
-                                F AS friend ),
-            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
-                   SOURCE KEY (user_id)
-                   DESTINATION KEY (friend)
-                   AS ( FROM    Yelp.Friends F
-                        SELECT  F.user_id AS user_id,
-                                F.friend AS friend )
-MATCH       (u2:User)<-[:FRIENDS_WITH]-(u1:User)
+                                F AS friend )
+MATCH       (u2:User)-[fw:FRIENDS_WITH]-(u1:User)
 SELECT      u1.user_id AS u1_user_id,
-            u2.user_id AS u2_user_id
-ORDER BY    u1_user_id,
-            u2_user_id;
\ No newline at end of file
+            u2.user_id AS u2_user_id,
+            u1.friends AS friends,
+            fw.user_id AS fw_user_id,
+            fw.friend AS fw_friend
+ORDER BY    u1_user_id, u2_user_id, fw_user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
deleted file mode 100644
index 298b5c8..0000000
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
+++ /dev/null
@@ -1,43 +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.
- */
-
-// One edge definition, single vertex definition on both sides, with a dangling vertex.
-FROM GRAPH  VERTEX (:User)
-                   PRIMARY KEY (user_id)
-                   AS Yelp.Users,
-            VERTEX (:Review)
-                   PRIMARY KEY (review_id)
-                   AS ( FROM   Yelp.Reviews R
-                        SELECT R.user_id AS review_user_id, R.review_id ),
-            EDGE   (:Review)-[:MADE_BY]->(:User)
-                   SOURCE KEY (review_id)
-                   DESTINATION KEY (user_id)
-                   AS ( FROM    Yelp.Reviews R
-                        SELECT  R.user_id, R.review_id )
-MATCH       (u:User)<-[:MADE_BY]-(r:Review), (n)
-SELECT      u.user_id AS u_user_id,
-            r.review_id AS r_review_id,
-            r.user_id AS r_user_id,
-            n.user_id AS n_user_id,
-            n.review_id AS n_review_id
-ORDER BY    u_user_id,
-            r_review_id,
-            r_user_id,
-            n_user_id,
-            n_review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.7.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.7.query.sqlpp
index 56a0672..513c87a 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.7.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.7.query.sqlpp
@@ -25,7 +25,7 @@
                    SOURCE KEY (user_id)
                    DESTINATION KEY (friend)
                    AS ( FROM    Yelp.Friends F
-                        SELECT  F.* )
+                        SELECT  VALUE F )
 MATCH       (u)-(v)-(w)-(y)
 SELECT      u.user_id AS u_user_id,
             v.user_id AS v_user_id,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.3.query.sqlpp
index c5c1d8d..6a1721c 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.3.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.3.query.sqlpp
@@ -28,10 +28,10 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)<-[fw:FRIENDS_WITH{1,3}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
+LET         fw_edge = ( FROM    fw.Edges FW
+                        SELECT  VALUE FW.friend )
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_count
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            fw_edge,
+            LEN(fw_edge) AS fw_count
+ORDER BY    u1_user_id, fw_edge, u2_user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.4.query.sqlpp
index 83a78b4..e03c56d 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.4.query.sqlpp
@@ -28,11 +28,10 @@
                         SELECT  F.user_id AS user_id,
                                 F.friend AS friend )
 MATCH       (u2:User)-[fw:FRIENDS_WITH{1,2}]-(u1:User)
-UNNEST      fw AS fwEdgeRecord
-LET         fw_left_vertex = VERTEX_KEY(fwEdgeRecord.LeftVertex)
+LET         fw_edge =    ( FROM    fw.Edges FW
+                           SELECT  VALUE FW.friend )
 SELECT      u1.user_id AS u1_user_id,
-            fw_left_vertex,
-            DIRECTION(fwEdgeRecord.`Edge`) AS dir,
             u2.user_id AS u2_user_id,
-            HOP_COUNT(fw) AS fw_hops
-ORDER BY    u1, fw_left_vertex, u2;
\ No newline at end of file
+            fw_edge,
+            LEN(fw_edge) AS fw_hops
+ORDER BY    u1_user_id, u2_user_id, fw_edge;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.3.adm
index 45a5043..3cd67b3 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.3.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.3.adm
@@ -1,8 +1,12 @@
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
\ No newline at end of file
+{ "fw_hops": 2, "u1_user_id": 1, "u2_user_id": 3, "path_user_id": 1 }
+{ "fw_hops": 2, "u1_user_id": 1, "u2_user_id": 3, "path_user_id": 2 }
+{ "fw_hops": 2, "u1_user_id": 1, "u2_user_id": 3, "path_user_id": 3 }
+{ "fw_hops": 2, "u1_user_id": 2, "u2_user_id": 4, "path_user_id": 2 }
+{ "fw_hops": 2, "u1_user_id": 2, "u2_user_id": 4, "path_user_id": 3 }
+{ "fw_hops": 2, "u1_user_id": 2, "u2_user_id": 4, "path_user_id": 4 }
+{ "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 5, "path_user_id": 3 }
+{ "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 5, "path_user_id": 4 }
+{ "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 5, "path_user_id": 5 }
+{ "fw_hops": 2, "u1_user_id": 4, "u2_user_id": 6, "path_user_id": 4 }
+{ "fw_hops": 2, "u1_user_id": 4, "u2_user_id": 6, "path_user_id": 5 }
+{ "fw_hops": 2, "u1_user_id": 4, "u2_user_id": 6, "path_user_id": 6 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.4.adm
index d7b0151..faeeb87 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.4.adm
@@ -1,9 +1,12 @@
-{ "fw_hops": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 4 }
-{ "fw_hops": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 4 }
-{ "fw_hops": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_hops": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 5 }
-{ "fw_hops": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 5 }
-{ "fw_hops": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_hops": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 6 }
-{ "fw_hops": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 6 }
-{ "fw_hops": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
\ No newline at end of file
+{ "fw_hops": 3, "u1_user_id": 1, "u2_user_id": 4, "path_user_id": 1 }
+{ "fw_hops": 3, "u1_user_id": 1, "u2_user_id": 4, "path_user_id": 2 }
+{ "fw_hops": 3, "u1_user_id": 1, "u2_user_id": 4, "path_user_id": 3 }
+{ "fw_hops": 3, "u1_user_id": 1, "u2_user_id": 4, "path_user_id": 4 }
+{ "fw_hops": 3, "u1_user_id": 2, "u2_user_id": 5, "path_user_id": 2 }
+{ "fw_hops": 3, "u1_user_id": 2, "u2_user_id": 5, "path_user_id": 3 }
+{ "fw_hops": 3, "u1_user_id": 2, "u2_user_id": 5, "path_user_id": 4 }
+{ "fw_hops": 3, "u1_user_id": 2, "u2_user_id": 5, "path_user_id": 5 }
+{ "fw_hops": 3, "u1_user_id": 3, "u2_user_id": 6, "path_user_id": 3 }
+{ "fw_hops": 3, "u1_user_id": 3, "u2_user_id": 6, "path_user_id": 4 }
+{ "fw_hops": 3, "u1_user_id": 3, "u2_user_id": 6, "path_user_id": 5 }
+{ "fw_hops": 3, "u1_user_id": 3, "u2_user_id": 6, "path_user_id": 6 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.5.adm
index eb23c4f..7fd84a1 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.5.adm
@@ -1,8 +1,2 @@
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 1, "fw1_left_vertex": { "user_id": 2 }, "u2_user_id": 3, "fw2_left_vertex": { "user_id": 4 }, "u3_user_id": 5 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 1, "fw1_left_vertex": { "user_id": 2 }, "u2_user_id": 3, "fw2_left_vertex": { "user_id": 5 }, "u3_user_id": 5 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 1, "fw1_left_vertex": { "user_id": 3 }, "u2_user_id": 3, "fw2_left_vertex": { "user_id": 4 }, "u3_user_id": 5 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 1, "fw1_left_vertex": { "user_id": 3 }, "u2_user_id": 3, "fw2_left_vertex": { "user_id": 5 }, "u3_user_id": 5 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 2, "fw1_left_vertex": { "user_id": 3 }, "u2_user_id": 4, "fw2_left_vertex": { "user_id": 5 }, "u3_user_id": 6 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 2, "fw1_left_vertex": { "user_id": 3 }, "u2_user_id": 4, "fw2_left_vertex": { "user_id": 6 }, "u3_user_id": 6 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 2, "fw1_left_vertex": { "user_id": 4 }, "u2_user_id": 4, "fw2_left_vertex": { "user_id": 5 }, "u3_user_id": 6 }
-{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 2, "fw1_left_vertex": { "user_id": 4 }, "u2_user_id": 4, "fw2_left_vertex": { "user_id": 6 }, "u3_user_id": 6 }
\ No newline at end of file
+{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 1, "u2_user_id": 3, "u3_user_id": 5 }
+{ "fw1_hops": 2, "fw2_hops": 2, "u1_user_id": 2, "u2_user_id": 4, "u3_user_id": 6 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.6.adm
index 6b486c7..5503c0e 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.6.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.6.adm
@@ -1,5 +1,5 @@
-{ "fw_hops": 1, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 2 }
-{ "fw_hops": 1, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_hops": 1, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_hops": 1, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_hops": 1, "u1_user_id": 5, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
\ No newline at end of file
+{ "fw_hops": 1, "u1_user_id": 1, "u2_user_id": 2 }
+{ "fw_hops": 1, "u1_user_id": 2, "u2_user_id": 3 }
+{ "fw_hops": 1, "u1_user_id": 3, "u2_user_id": 4 }
+{ "fw_hops": 1, "u1_user_id": 4, "u2_user_id": 5 }
+{ "fw_hops": 1, "u1_user_id": 5, "u2_user_id": 6 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.8.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.8.adm
index 8d03172..0bdf79c 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.8.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.8.adm
@@ -1,20 +1,10 @@
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 1 }, "u2_user_id": 1 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 1 }, "u2_user_id": 1 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 1 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 1 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 2 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 2 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 5, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 5, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 6, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 6, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 4 }
\ No newline at end of file
+{ "fwVertices": [ 3, 2, 1 ], "fwEdges": [ 3, 1 ], "fw_hops": 2, "u1_user_id": 1, "u2_user_id": 3 }
+{ "fwVertices": [ 3, 2, 1 ], "fwEdges": [ 3, 2 ], "fw_hops": 2, "u1_user_id": 1, "u2_user_id": 3 }
+{ "fwVertices": [ 4, 3, 2 ], "fwEdges": [ 4, 3 ], "fw_hops": 2, "u1_user_id": 2, "u2_user_id": 4 }
+{ "fwVertices": [ 1, 2, 3 ], "fwEdges": [ 1, 3 ], "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 1 }
+{ "fwVertices": [ 1, 2, 3 ], "fwEdges": [ 2, 3 ], "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 1 }
+{ "fwVertices": [ 5, 4, 3 ], "fwEdges": [ 5, 4 ], "fw_hops": 2, "u1_user_id": 3, "u2_user_id": 5 }
+{ "fwVertices": [ 2, 3, 4 ], "fwEdges": [ 3, 4 ], "fw_hops": 2, "u1_user_id": 4, "u2_user_id": 2 }
+{ "fwVertices": [ 6, 5, 4 ], "fwEdges": [ 6, 5 ], "fw_hops": 2, "u1_user_id": 4, "u2_user_id": 6 }
+{ "fwVertices": [ 3, 4, 5 ], "fwEdges": [ 4, 5 ], "fw_hops": 2, "u1_user_id": 5, "u2_user_id": 3 }
+{ "fwVertices": [ 4, 5, 6 ], "fwEdges": [ 5, 6 ], "fw_hops": 2, "u1_user_id": 6, "u2_user_id": 4 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
index d746e73..c514419 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
@@ -1,8 +1,8 @@
-{ "vertexProperties": { "review_id": "A", "user_id": 1 }, "vertexDetail": { "Label": "Review", "PrimaryKey": { "review_id": "A" } }, "vertexKey": { "review_id": "A" }, "vertexLabel": "Review" }
-{ "vertexProperties": { "review_id": "B", "user_id": 2 }, "vertexDetail": { "Label": "Review", "PrimaryKey": { "review_id": "B" } }, "vertexKey": { "review_id": "B" }, "vertexLabel": "Review" }
-{ "vertexProperties": { "user_id": 1 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 1 } }, "vertexKey": { "user_id": 1 }, "vertexLabel": "User" }
-{ "vertexProperties": { "user_id": 2 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 2 } }, "vertexKey": { "user_id": 2 }, "vertexLabel": "User" }
-{ "vertexProperties": { "user_id": 3 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 3 } }, "vertexKey": { "user_id": 3 }, "vertexLabel": "User" }
-{ "vertexProperties": { "user_id": 4 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 4 } }, "vertexKey": { "user_id": 4 }, "vertexLabel": "User" }
-{ "vertexProperties": { "user_id": 5 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 5 } }, "vertexKey": { "user_id": 5 }, "vertexLabel": "User" }
-{ "vertexProperties": { "user_id": 6 }, "vertexDetail": { "Label": "User", "PrimaryKey": { "user_id": 6 } }, "vertexKey": { "user_id": 6 }, "vertexLabel": "User" }
\ No newline at end of file
+{ "vertexLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "A" ] } }
+{ "vertexLabel": "Review", "vertexDetail": { "ElementLabel": "Review", "VertexKey": [ "B" ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 1 ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 2 ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 3 ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 4 ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 5 ] } }
+{ "vertexLabel": "User", "vertexDetail": { "ElementLabel": "User", "VertexKey": [ 6 ] } }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
index a4b2231..c81b251 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
@@ -1,14 +1,14 @@
-{ "sourceVertex": { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } }, "destVertex": { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "edgeDetail": { "Label": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } }, "edgeProperties": { "review_id": "A", "user_id": 1 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "review_id": "A" }, "destKeyValue": { "user_id": 1 }, "edgeLabel": "MADE_BY", "n1Label": "Review", "n2Label": "User" }
-{ "sourceVertex": { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } }, "destVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "edgeDetail": { "Label": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 2 } }, "edgeProperties": { "review_id": "B", "user_id": 2 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "review_id": "B" }, "destKeyValue": { "user_id": 2 }, "edgeLabel": "MADE_BY", "n1Label": "Review", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "destVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } }, "edgeProperties": { "user_id": 1, "friend": 2 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "user_id": 1 }, "destKeyValue": { "friend": 2 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } }, "destVertex": { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "edgeDetail": { "Label": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } }, "edgeProperties": { "review_id": "A", "user_id": 1 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "review_id": "A" }, "destKeyValue": { "user_id": 1 }, "edgeLabel": "MADE_BY", "n1Label": "User", "n2Label": "Review" }
-{ "sourceVertex": { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "destVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } }, "edgeProperties": { "user_id": 1, "friend": 2 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "user_id": 1 }, "destKeyValue": { "friend": 2 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "destVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } }, "edgeProperties": { "user_id": 2, "friend": 3 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "user_id": 2 }, "destKeyValue": { "friend": 3 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } }, "destVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "edgeDetail": { "Label": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 2 } }, "edgeProperties": { "review_id": "B", "user_id": 2 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "review_id": "B" }, "destKeyValue": { "user_id": 2 }, "edgeLabel": "MADE_BY", "n1Label": "User", "n2Label": "Review" }
-{ "sourceVertex": { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "destVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } }, "edgeProperties": { "user_id": 2, "friend": 3 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "user_id": 2 }, "destKeyValue": { "friend": 3 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "destVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 3 }, "DestinationKey": { "friend": 4 } }, "edgeProperties": { "user_id": 3, "friend": 4 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "user_id": 3 }, "destKeyValue": { "friend": 4 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "destVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 3 }, "DestinationKey": { "friend": 4 } }, "edgeProperties": { "user_id": 3, "friend": 4 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "user_id": 3 }, "destKeyValue": { "friend": 4 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } }, "destVertex": { "user_id": 5, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 5 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 4 }, "DestinationKey": { "friend": 5 } }, "edgeProperties": { "user_id": 4, "friend": 5 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "user_id": 4 }, "destKeyValue": { "friend": 5 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } }, "destVertex": { "user_id": 5, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 5 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 4 }, "DestinationKey": { "friend": 5 } }, "edgeProperties": { "user_id": 4, "friend": 5 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "user_id": 4 }, "destKeyValue": { "friend": 5 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 5, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 5 } } }, "destVertex": { "user_id": 6, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 6 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 5 }, "DestinationKey": { "friend": 6 } }, "edgeProperties": { "user_id": 5, "friend": 6 }, "direction": "LEFT_TO_RIGHT", "sourceKeyValue": { "user_id": 5 }, "destKeyValue": { "friend": 6 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
-{ "sourceVertex": { "user_id": 5, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 5 } } }, "destVertex": { "user_id": 6, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 6 } } }, "edgeDetail": { "Label": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 5 }, "DestinationKey": { "friend": 6 } }, "edgeProperties": { "user_id": 5, "friend": 6 }, "direction": "RIGHT_TO_LEFT", "sourceKeyValue": { "user_id": 5 }, "destKeyValue": { "friend": 6 }, "edgeLabel": "FRIENDS_WITH", "n1Label": "User", "n2Label": "User" }
\ No newline at end of file
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "Review", "n2Label": "User", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
+{ "direction": "LEFT_TO_RIGHT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "Review", "n2Label": "User", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 1 ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 2 ], "DestinationKey": [ 3 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 3 ], "DestinationKey": [ 4 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 4 ], "DestinationKey": [ 5 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "FRIENDS_WITH", "edgeDetail": { "ElementLabel": "FRIENDS_WITH", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ 5 ], "DestinationKey": [ 6 ] }, "n1Label": "User", "n2Label": "User", "sourceVertexLabel": "User", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "A" ], "DestinationKey": [ 1 ] }, "n1Label": "User", "n2Label": "Review", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
+{ "direction": "RIGHT_TO_LEFT", "edgeLabel": "MADE_BY", "edgeDetail": { "ElementLabel": "MADE_BY", "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": [ "B" ], "DestinationKey": [ 2 ] }, "n1Label": "User", "n2Label": "Review", "sourceVertexLabel": "Review", "destVertexLabel": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.5.adm
index 6fab046..e1cdc9a 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.5.adm
@@ -1,4 +1,7 @@
-{ "pEdges": [ { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } } }, { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } } ], "eEdges": [ { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } } } ], "pHopCount": 2, "pLabels": [ "FRIENDS_WITH", "MADE_BY", "Review", "User" ], "eLabels": [ "MADE_BY", "Review", "User" ], "pVertices": [ { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } }, { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ], "eVertices": [ { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } }, { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } } ] }
-{ "pEdges": [ { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 2 } } }, { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } } ], "eEdges": [ { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 2 } } } ], "pHopCount": 2, "pLabels": [ "FRIENDS_WITH", "MADE_BY", "Review", "User" ], "eLabels": [ "MADE_BY", "Review", "User" ], "pVertices": [ { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } }, { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ], "eVertices": [ { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ] }
-{ "pEdges": [ { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 2 } } } ], "eEdges": [ { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } } ], "pHopCount": 2, "pLabels": [ "FRIENDS_WITH", "MADE_BY", "Review", "User" ], "eLabels": [ "FRIENDS_WITH", "User" ], "pVertices": [ { "review_id": "B", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } }, { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ], "eVertices": [ { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ] }
-{ "pEdges": [ { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } } } ], "eEdges": [ { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } } ], "pHopCount": 2, "pLabels": [ "FRIENDS_WITH", "MADE_BY", "Review", "User" ], "eLabels": [ "FRIENDS_WITH", "User" ], "pVertices": [ { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } }, { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ], "eVertices": [ { "user_id": 1, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, { "user_id": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } ] }
\ No newline at end of file
+{ "pEdges": [ { "user_id": 1, "friend": 2 }, { "user_id": 2, "friend": 3 } ], "eEdges": [ { "user_id": 1, "friend": 2 } ], "pHopCount": 2, "pVertices": [ { "user_id": 1 }, { "user_id": 2 }, { "user_id": 3 } ], "eVertices": [ { "user_id": 1 }, { "user_id": 2 } ] }
+{ "pEdges": [ { "user_id": 1, "friend": 2 }, { "user_id": 2, "friend": 3 }, { "user_id": 3, "friend": 4 } ], "eEdges": [ { "user_id": 1, "friend": 2 }, { "user_id": 2, "friend": 3 } ], "pHopCount": 3, "pVertices": [ { "user_id": 1 }, { "user_id": 2 }, { "user_id": 3 }, { "user_id": 4 } ], "eVertices": [ { "user_id": 1 }, { "user_id": 2 }, { "user_id": 3 } ] }
+{ "pEdges": [ { "user_id": 2, "friend": 3 }, { "user_id": 3, "friend": 4 } ], "eEdges": [ { "user_id": 2, "friend": 3 } ], "pHopCount": 2, "pVertices": [ { "user_id": 2 }, { "user_id": 3 }, { "user_id": 4 } ], "eVertices": [ { "user_id": 2 }, { "user_id": 3 } ] }
+{ "pEdges": [ { "user_id": 2, "friend": 3 }, { "user_id": 3, "friend": 4 }, { "user_id": 4, "friend": 5 } ], "eEdges": [ { "user_id": 2, "friend": 3 }, { "user_id": 3, "friend": 4 } ], "pHopCount": 3, "pVertices": [ { "user_id": 2 }, { "user_id": 3 }, { "user_id": 4 }, { "user_id": 5 } ], "eVertices": [ { "user_id": 2 }, { "user_id": 3 }, { "user_id": 4 } ] }
+{ "pEdges": [ { "user_id": 3, "friend": 4 }, { "user_id": 4, "friend": 5 } ], "eEdges": [ { "user_id": 3, "friend": 4 } ], "pHopCount": 2, "pVertices": [ { "user_id": 3 }, { "user_id": 4 }, { "user_id": 5 } ], "eVertices": [ { "user_id": 3 }, { "user_id": 4 } ] }
+{ "pEdges": [ { "user_id": 3, "friend": 4 }, { "user_id": 4, "friend": 5 }, { "user_id": 5, "friend": 6 } ], "eEdges": [ { "user_id": 3, "friend": 4 }, { "user_id": 4, "friend": 5 } ], "pHopCount": 3, "pVertices": [ { "user_id": 3 }, { "user_id": 4 }, { "user_id": 5 }, { "user_id": 6 } ], "eVertices": [ { "user_id": 3 }, { "user_id": 4 }, { "user_id": 5 } ] }
+{ "pEdges": [ { "user_id": 4, "friend": 5 }, { "user_id": 5, "friend": 6 } ], "eEdges": [ { "user_id": 4, "friend": 5 } ], "pHopCount": 2, "pVertices": [ { "user_id": 4 }, { "user_id": 5 }, { "user_id": 6 } ], "eVertices": [ { "user_id": 4 }, { "user_id": 5 } ] }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm
index 5a87275..89677c3 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm
@@ -1 +1 @@
-{ "e_length": 2, "e_label": "FRIENDS_WITH", "n_label": "User" }
\ No newline at end of file
+{ "e_label": "FRIENDS_WITH", "n_label": "User", "e_length": 2 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.4.adm
deleted file mode 100644
index 9a221cf..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.4.adm
+++ /dev/null
@@ -1,2 +0,0 @@
-{ "u1_user_id": 1, "fw_user_id": 1, "fw_friend": 2, "u2_user_id": 2 }
-{ "u1_user_id": 2, "fw_user_id": 2, "fw_friend": 3, "u2_user_id": 3, "mb_user_id": 2, "mb_review_id": "A", "r_review_id": "A" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.5.adm
index 5c82883..9a221cf 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.5.adm
@@ -1,5 +1,2 @@
-{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 2, "u2_user_id": 2, "u3_user_id": 3, "mb_user_id": 3, "mb_review_id": "A", "r_review_id": "A", "fw2_user_id": 3, "fw2_friend": 1 }
-{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 2, "u2_user_id": 2, "u3_user_id": 4, "mb_user_id": 4, "mb_review_id": "B", "r_review_id": "B", "fw2_user_id": 4, "fw2_friend": 1 }
-{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 4, "u2_user_id": 4, "u3_user_id": 3, "mb_user_id": 3, "mb_review_id": "A", "r_review_id": "A", "fw2_user_id": 3, "fw2_friend": 1 }
-{ "u1_user_id": 3, "fw1_user_id": 3, "fw1_friend": 1, "u2_user_id": 1 }
-{ "u1_user_id": 4, "fw1_user_id": 4, "fw1_friend": 1, "u2_user_id": 1 }
\ No newline at end of file
+{ "u1_user_id": 1, "fw_user_id": 1, "fw_friend": 2, "u2_user_id": 2 }
+{ "u1_user_id": 2, "fw_user_id": 2, "fw_friend": 3, "u2_user_id": 3, "mb_user_id": 2, "mb_review_id": "A", "r_review_id": "A" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.7.adm
new file mode 100644
index 0000000..5c82883
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.7.adm
@@ -0,0 +1,5 @@
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 2, "u2_user_id": 2, "u3_user_id": 3, "mb_user_id": 3, "mb_review_id": "A", "r_review_id": "A", "fw2_user_id": 3, "fw2_friend": 1 }
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 2, "u2_user_id": 2, "u3_user_id": 4, "mb_user_id": 4, "mb_review_id": "B", "r_review_id": "B", "fw2_user_id": 4, "fw2_friend": 1 }
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 4, "u2_user_id": 4, "u3_user_id": 3, "mb_user_id": 3, "mb_review_id": "A", "r_review_id": "A", "fw2_user_id": 3, "fw2_friend": 1 }
+{ "u1_user_id": 3, "fw1_user_id": 3, "fw1_friend": 1, "u2_user_id": 1 }
+{ "u1_user_id": 4, "fw1_user_id": 4, "fw1_friend": 1, "u2_user_id": 1 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.9.adm
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.6.adm
rename to asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.9.adm
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.3.adm
deleted file mode 100644
index bf58f6a..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.3.adm
+++ /dev/null
@@ -1,4 +0,0 @@
-{ "user_id": 1 }
-{ "user_id": 2 }
-{ "user_id": 3 }
-{ "user_id": 4 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.4.adm
deleted file mode 100644
index 9fd8681..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.4.adm
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "user_id": 1 }
-{ "user_id": 1, "review_id": "A" }
-{ "user_id": 1, "review_id": "B" }
-{ "user_id": 2 }
-{ "user_id": 2, "review_id": "C" }
-{ "user_id": 3 }
-{ "user_id": 3, "review_id": "D" }
-{ "user_id": 4 }
-{ "user_id": 4, "review_id": "E" }
-{ "user_id": 5, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.5.adm
deleted file mode 100644
index 843d7f9..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.5.adm
+++ /dev/null
@@ -1,10 +0,0 @@
-{ "left_review_id": "A", "right_user_id": 1, "e_user_id": 1, "e_review_id": "A" }
-{ "left_review_id": "B", "right_user_id": 1, "e_user_id": 1, "e_review_id": "B" }
-{ "left_review_id": "C", "right_user_id": 2, "e_user_id": 2, "e_review_id": "C" }
-{ "left_review_id": "D", "right_user_id": 3, "e_user_id": 3, "e_review_id": "D" }
-{ "left_review_id": "E", "right_user_id": 4, "e_user_id": 4, "e_review_id": "E" }
-{ "left_user_id": 1, "right_review_id": "A", "e_user_id": 1, "e_review_id": "A" }
-{ "left_user_id": 1, "right_review_id": "B", "e_user_id": 1, "e_review_id": "B" }
-{ "left_user_id": 2, "right_review_id": "C", "e_user_id": 2, "e_review_id": "C" }
-{ "left_user_id": 3, "right_review_id": "D", "e_user_id": 3, "e_review_id": "D" }
-{ "left_user_id": 4, "right_review_id": "E", "e_user_id": 4, "e_review_id": "E" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.6.adm
deleted file mode 100644
index b451053..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.6.adm
+++ /dev/null
@@ -1,8 +0,0 @@
-{ "source_user_id": 1, "dest_user_id": 2, "e_user_id": 1, "e_friend": 2 }
-{ "source_user_id": 3, "dest_user_id": 2, "e_user_id": 3, "e_friend": 2 }
-{ "source_user_id": 4, "dest_user_id": 2, "e_user_id": 4, "e_friend": 2 }
-{ "source_review_id": "A", "dest_user_id": 1, "e_user_id": 1, "e_review_id": "A" }
-{ "source_review_id": "B", "dest_user_id": 1, "e_user_id": 1, "e_review_id": "B" }
-{ "source_review_id": "C", "dest_user_id": 2, "e_user_id": 2, "e_review_id": "C" }
-{ "source_review_id": "D", "dest_user_id": 3, "e_user_id": 3, "e_review_id": "D" }
-{ "source_review_id": "E", "dest_user_id": 4, "e_user_id": 4, "e_review_id": "E" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.3.adm
index dc47f05..a45ec54 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.3.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.3.adm
@@ -1,4 +1,4 @@
-{ "pathLength": 0, "edgeRecord": { "Edge": null, "RightVertex": null, "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } } } }
-{ "pathLength": 0, "edgeRecord": { "Edge": null, "RightVertex": null, "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
-{ "pathLength": 0, "edgeRecord": { "Edge": null, "RightVertex": null, "LeftVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } } } }
-{ "pathLength": 0, "edgeRecord": { "Edge": null, "RightVertex": null, "LeftVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } } } }
\ No newline at end of file
+{ "pathLength": 0, "pathVertex": { "user_id": 1, "best_friend": 2 } }
+{ "pathLength": 0, "pathVertex": { "user_id": 2, "best_friend": 3 } }
+{ "pathLength": 0, "pathVertex": { "user_id": 3 } }
+{ "pathLength": 0, "pathVertex": { "user_id": 4 } }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.4.adm
index be295c8..7f3d7e1 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.4.adm
@@ -1,6 +1,6 @@
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "A" }, "DestinationKey": { "user_id": 1 } } }, "RightVertex": { "review_id": "A", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "A" } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "review_id": "B", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "B" }, "DestinationKey": { "user_id": 1 } } }, "RightVertex": { "review_id": "B", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "B" } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "review_id": "C", "user_id": 1, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "C" }, "DestinationKey": { "user_id": 1 } } }, "RightVertex": { "review_id": "C", "user_id": 1, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "C" } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "review_id": "D", "user_id": 2, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "D" }, "DestinationKey": { "user_id": 2 } } }, "RightVertex": { "review_id": "D", "user_id": 2, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "D" } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "Edge": { "review_id": "E", "user_id": 3, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "E" }, "DestinationKey": { "user_id": 3 } } }, "RightVertex": { "review_id": "E", "user_id": 3, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "E" } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 4, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 4 } } }, "Edge": { "review_id": "F", "user_id": 4, "_GraphixElementDetail": { "Label": "MADE_BY" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "review_id": "F" }, "DestinationKey": { "user_id": 4 } } }, "RightVertex": { "review_id": "F", "user_id": 4, "_GraphixElementDetail": { "Label": "Review" }, "_GraphixVertexDetail": { "PrimaryKey": { "review_id": "F" } } } } }
\ No newline at end of file
+{ "pathLength": 1, "edgeRecord": { "review_id": "A", "user_id": 1 } }
+{ "pathLength": 1, "edgeRecord": { "review_id": "B", "user_id": 1 } }
+{ "pathLength": 1, "edgeRecord": { "review_id": "C", "user_id": 1 } }
+{ "pathLength": 1, "edgeRecord": { "review_id": "D", "user_id": 2 } }
+{ "pathLength": 1, "edgeRecord": { "review_id": "E", "user_id": 3 } }
+{ "pathLength": 1, "edgeRecord": { "review_id": "F", "user_id": 4 } }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.5.adm
index 94e906a..1554fbb 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.5.adm
@@ -1,4 +1,4 @@
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
\ No newline at end of file
+{ "pathLength": 2, "edgeRecord": { "user_id": 1, "friend": 2 }, "u_user_id": 1, "v_user_id": 2, "w_user_id": 3 }
+{ "pathLength": 2, "edgeRecord": { "user_id": 2, "friend": 3 }, "u_user_id": 1, "v_user_id": 2, "w_user_id": 3 }
+{ "pathLength": 2, "edgeRecord": { "user_id": 1, "friend": 2 }, "u_user_id": 3, "v_user_id": 2, "w_user_id": 1 }
+{ "pathLength": 2, "edgeRecord": { "user_id": 2, "friend": 3 }, "u_user_id": 3, "v_user_id": 2, "w_user_id": 1 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.6.adm
index ba46ed7..0277ad3 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.6.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.6.adm
@@ -1,8 +1,6 @@
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "LEFT_TO_RIGHT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } }, "Edge": { "user_id": 1, "friend": 2, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 1 }, "DestinationKey": { "friend": 2 } } }, "RightVertex": { "user_id": 1, "best_friend": 2, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 1 } } } } }
-{ "pathLength": 2, "edgeRecord": { "LeftVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
-{ "pathLength": 1, "edgeRecord": { "LeftVertex": { "user_id": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 3 } } }, "Edge": { "user_id": 2, "friend": 3, "_GraphixElementDetail": { "Label": "FRIENDS_WITH" }, "_GraphixEdgeDetail": { "EdgeDirection": "RIGHT_TO_LEFT", "SourceKey": { "user_id": 2 }, "DestinationKey": { "friend": 3 } } }, "RightVertex": { "user_id": 2, "best_friend": 3, "_GraphixElementDetail": { "Label": "User" }, "_GraphixVertexDetail": { "PrimaryKey": { "user_id": 2 } } } } }
\ No newline at end of file
+{ "pathLength": 1, "u_user_id": 1, "v_user_id": 2 }
+{ "pathLength": 2, "u_user_id": 1, "v_user_id": 3 }
+{ "pathLength": 1, "u_user_id": 2, "v_user_id": 1 }
+{ "pathLength": 1, "u_user_id": 2, "v_user_id": 3 }
+{ "pathLength": 2, "u_user_id": 3, "v_user_id": 1 }
+{ "pathLength": 1, "u_user_id": 3, "v_user_id": 2 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
index 802c33a..725fe9c 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
@@ -1,6 +1,6 @@
-{ "shortestPath": [ 1, 2 ], "u1_user_id": 1, "u2_user_id": 2 }
-{ "shortestPath": [ 1, 3 ], "u1_user_id": 1, "u2_user_id": 3 }
-{ "shortestPath": [ 1, 3, 4 ], "u1_user_id": 1, "u2_user_id": 4 }
-{ "shortestPath": [ 2, 3 ], "u1_user_id": 2, "u2_user_id": 3 }
-{ "shortestPath": [ 2, 3, 4 ], "u1_user_id": 2, "u2_user_id": 4 }
-{ "shortestPath": [ 3, 4 ], "u1_user_id": 3, "u2_user_id": 4 }
\ No newline at end of file
+{ "u1_user_id": 1, "u2_user_id": 2, "shortestPath": [ 1, 2 ] }
+{ "u1_user_id": 1, "u2_user_id": 3, "shortestPath": [ 1, 3 ] }
+{ "u1_user_id": 1, "u2_user_id": 4, "shortestPath": [ 1, 3, 4 ] }
+{ "u1_user_id": 2, "u2_user_id": 3, "shortestPath": [ 2, 3 ] }
+{ "u1_user_id": 2, "u2_user_id": 4, "shortestPath": [ 2, 3, 4 ] }
+{ "u1_user_id": 3, "u2_user_id": 4, "shortestPath": [ 3, 4 ] }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm
index 802c33a..725fe9c 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm
@@ -1,6 +1,6 @@
-{ "shortestPath": [ 1, 2 ], "u1_user_id": 1, "u2_user_id": 2 }
-{ "shortestPath": [ 1, 3 ], "u1_user_id": 1, "u2_user_id": 3 }
-{ "shortestPath": [ 1, 3, 4 ], "u1_user_id": 1, "u2_user_id": 4 }
-{ "shortestPath": [ 2, 3 ], "u1_user_id": 2, "u2_user_id": 3 }
-{ "shortestPath": [ 2, 3, 4 ], "u1_user_id": 2, "u2_user_id": 4 }
-{ "shortestPath": [ 3, 4 ], "u1_user_id": 3, "u2_user_id": 4 }
\ No newline at end of file
+{ "u1_user_id": 1, "u2_user_id": 2, "shortestPath": [ 1, 2 ] }
+{ "u1_user_id": 1, "u2_user_id": 3, "shortestPath": [ 1, 3 ] }
+{ "u1_user_id": 1, "u2_user_id": 4, "shortestPath": [ 1, 3, 4 ] }
+{ "u1_user_id": 2, "u2_user_id": 3, "shortestPath": [ 2, 3 ] }
+{ "u1_user_id": 2, "u2_user_id": 4, "shortestPath": [ 2, 3, 4 ] }
+{ "u1_user_id": 3, "u2_user_id": 4, "shortestPath": [ 3, 4 ] }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm
index d340a90..48e9814 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm
@@ -1,6 +1,40 @@
-{ "u1_user_id": 1, "u2_user_id": 2, "fw_user_id": 1, "fw_friend": 2 }
-{ "u1_user_id": 1, "u2_user_id": 3, "fw_user_id": 1, "fw_friend": 3 }
-{ "u1_user_id": 2, "u2_user_id": 1, "fw_user_id": 2, "fw_friend": 1 }
-{ "u1_user_id": 3, "u2_user_id": 1, "fw_user_id": 3, "fw_friend": 1 }
-{ "u1_user_id": 4, "u2_user_id": 1, "fw_user_id": 4, "fw_friend": 1 }
-{ "u1_user_id": 4, "u2_user_id": 2, "fw_user_id": 4, "fw_friend": 2 }
\ No newline at end of file
+{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "B" }
+{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "C" }
+{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "D" }
+{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "E" }
+{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "F" }
+{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 2 }
+{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 3 }
+{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 4 }
+{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "A" }
+{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "C" }
+{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "D" }
+{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "E" }
+{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "F" }
+{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 2 }
+{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 3 }
+{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 4 }
+{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "A" }
+{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "B" }
+{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "D" }
+{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "E" }
+{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "F" }
+{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 1 }
+{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 3 }
+{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 4 }
+{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "A" }
+{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "B" }
+{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "C" }
+{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "E" }
+{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "F" }
+{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 1 }
+{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 2 }
+{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 4 }
+{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "A" }
+{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "B" }
+{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "C" }
+{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "D" }
+{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "F" }
+{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 1 }
+{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 2 }
+{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.5.adm
index 190ae9b..c644784 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.5.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.5.adm
@@ -1,6 +1,8 @@
-{ "u1_user_id": 1, "u2_user_id": 2 }
-{ "u1_user_id": 1, "u2_user_id": 3 }
-{ "u1_user_id": 2, "u2_user_id": 1 }
-{ "u1_user_id": 3, "u2_user_id": 1 }
-{ "u1_user_id": 4, "u2_user_id": 1 }
-{ "u1_user_id": 4, "u2_user_id": 2 }
\ No newline at end of file
+{ "u1_user_id": 1, "u2_user_id": 2, "friends": [ 2, 3 ], "fw_user_id": 1, "fw_friend": 2 }
+{ "u1_user_id": 1, "u2_user_id": 2, "friends": [ 2, 3 ], "fw_user_id": 2, "fw_friend": 1 }
+{ "u1_user_id": 1, "u2_user_id": 3, "friends": [ 2, 3 ], "fw_user_id": 1, "fw_friend": 3 }
+{ "u1_user_id": 1, "u2_user_id": 3, "friends": [ 2, 3 ], "fw_user_id": 3, "fw_friend": 1 }
+{ "u1_user_id": 2, "u2_user_id": 1, "friends": [ 1 ], "fw_user_id": 1, "fw_friend": 2 }
+{ "u1_user_id": 2, "u2_user_id": 1, "friends": [ 1 ], "fw_user_id": 2, "fw_friend": 1 }
+{ "u1_user_id": 3, "u2_user_id": 1, "friends": [ 1 ], "fw_user_id": 1, "fw_friend": 3 }
+{ "u1_user_id": 3, "u2_user_id": 1, "friends": [ 1 ], "fw_user_id": 3, "fw_friend": 1 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
deleted file mode 100644
index 48e9814..0000000
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
+++ /dev/null
@@ -1,40 +0,0 @@
-{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "B" }
-{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "C" }
-{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "D" }
-{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "E" }
-{ "u_user_id": 1, "r_review_id": "A", "n_review_id": "F" }
-{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 2 }
-{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 3 }
-{ "u_user_id": 1, "r_review_id": "A", "n_user_id": 4 }
-{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "A" }
-{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "C" }
-{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "D" }
-{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "E" }
-{ "u_user_id": 1, "r_review_id": "B", "n_review_id": "F" }
-{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 2 }
-{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 3 }
-{ "u_user_id": 1, "r_review_id": "B", "n_user_id": 4 }
-{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "A" }
-{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "B" }
-{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "D" }
-{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "E" }
-{ "u_user_id": 2, "r_review_id": "C", "n_review_id": "F" }
-{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 1 }
-{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 3 }
-{ "u_user_id": 2, "r_review_id": "C", "n_user_id": 4 }
-{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "A" }
-{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "B" }
-{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "C" }
-{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "E" }
-{ "u_user_id": 3, "r_review_id": "D", "n_review_id": "F" }
-{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 1 }
-{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 2 }
-{ "u_user_id": 3, "r_review_id": "D", "n_user_id": 4 }
-{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "A" }
-{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "B" }
-{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "C" }
-{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "D" }
-{ "u_user_id": 4, "r_review_id": "E", "n_review_id": "F" }
-{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 1 }
-{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 2 }
-{ "u_user_id": 4, "r_review_id": "E", "n_user_id": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.3.adm
index 5f0bdb6..f0c3f6a 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.3.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.3.adm
@@ -1,22 +1,12 @@
-{ "fw_count": 1, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 2 }
-{ "fw_count": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 3 }
-{ "fw_count": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "u2_user_id": 4 }
-{ "fw_count": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_count": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 4 }
-{ "fw_count": 3, "u1_user_id": 1, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_count": 1, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 3 }
-{ "fw_count": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 4 }
-{ "fw_count": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "u2_user_id": 5 }
-{ "fw_count": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_count": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 5 }
-{ "fw_count": 3, "u1_user_id": 2, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_count": 1, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 4 }
-{ "fw_count": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 5 }
-{ "fw_count": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "u2_user_id": 6 }
-{ "fw_count": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_count": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 6 }
-{ "fw_count": 3, "u1_user_id": 3, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
-{ "fw_count": 1, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 5 }
-{ "fw_count": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "u2_user_id": 6 }
-{ "fw_count": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
-{ "fw_count": 1, "u1_user_id": 5, "fw_left_vertex": { "user_id": 6 }, "u2_user_id": 6 }
\ No newline at end of file
+{ "u1_user_id": 1, "u2_user_id": 2, "fw_edge": [ 2 ], "fw_count": 1 }
+{ "u1_user_id": 1, "u2_user_id": 3, "fw_edge": [ 3, 2 ], "fw_count": 2 }
+{ "u1_user_id": 1, "u2_user_id": 4, "fw_edge": [ 4, 3, 2 ], "fw_count": 3 }
+{ "u1_user_id": 2, "u2_user_id": 3, "fw_edge": [ 3 ], "fw_count": 1 }
+{ "u1_user_id": 2, "u2_user_id": 4, "fw_edge": [ 4, 3 ], "fw_count": 2 }
+{ "u1_user_id": 2, "u2_user_id": 5, "fw_edge": [ 5, 4, 3 ], "fw_count": 3 }
+{ "u1_user_id": 3, "u2_user_id": 4, "fw_edge": [ 4 ], "fw_count": 1 }
+{ "u1_user_id": 3, "u2_user_id": 5, "fw_edge": [ 5, 4 ], "fw_count": 2 }
+{ "u1_user_id": 3, "u2_user_id": 6, "fw_edge": [ 6, 5, 4 ], "fw_count": 3 }
+{ "u1_user_id": 4, "u2_user_id": 5, "fw_edge": [ 5 ], "fw_count": 1 }
+{ "u1_user_id": 4, "u2_user_id": 6, "fw_edge": [ 6, 5 ], "fw_count": 2 }
+{ "u1_user_id": 5, "u2_user_id": 6, "fw_edge": [ 6 ], "fw_count": 1 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.4.adm
index 581cb48..0d0e62a 100644
--- a/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.4.adm
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.4.adm
@@ -1,26 +1,18 @@
-{ "fw_hops": 1, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 2 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 2 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 1, "fw_left_vertex": { "user_id": 3 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 3 }
-{ "fw_hops": 1, "u1_user_id": 2, "fw_left_vertex": { "user_id": 1 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 1 }
-{ "fw_hops": 1, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 3 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 2, "fw_left_vertex": { "user_id": 4 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 1 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 1 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 2 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 1 }
-{ "fw_hops": 1, "u1_user_id": 3, "fw_left_vertex": { "user_id": 2 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 2 }
-{ "fw_hops": 1, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 4 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 3, "fw_left_vertex": { "user_id": 5 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 2 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 2 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 3 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 2 }
-{ "fw_hops": 1, "u1_user_id": 4, "fw_left_vertex": { "user_id": 3 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 3 }
-{ "fw_hops": 1, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 5 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 5 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 4, "fw_left_vertex": { "user_id": 6 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 5, "fw_left_vertex": { "user_id": 3 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 3 }
-{ "fw_hops": 2, "u1_user_id": 5, "fw_left_vertex": { "user_id": 4 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 3 }
-{ "fw_hops": 1, "u1_user_id": 5, "fw_left_vertex": { "user_id": 4 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 4 }
-{ "fw_hops": 1, "u1_user_id": 5, "fw_left_vertex": { "user_id": 6 }, "dir": "RIGHT_TO_LEFT", "u2_user_id": 6 }
-{ "fw_hops": 2, "u1_user_id": 6, "fw_left_vertex": { "user_id": 4 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 4 }
-{ "fw_hops": 2, "u1_user_id": 6, "fw_left_vertex": { "user_id": 5 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 4 }
-{ "fw_hops": 1, "u1_user_id": 6, "fw_left_vertex": { "user_id": 5 }, "dir": "LEFT_TO_RIGHT", "u2_user_id": 5 }
\ No newline at end of file
+{ "u1_user_id": 1, "u2_user_id": 2, "fw_edge": [ 2 ], "fw_hops": 1 }
+{ "u1_user_id": 1, "u2_user_id": 3, "fw_edge": [ 3, 2 ], "fw_hops": 2 }
+{ "u1_user_id": 2, "u2_user_id": 1, "fw_edge": [ 2 ], "fw_hops": 1 }
+{ "u1_user_id": 2, "u2_user_id": 3, "fw_edge": [ 3 ], "fw_hops": 1 }
+{ "u1_user_id": 2, "u2_user_id": 4, "fw_edge": [ 4, 3 ], "fw_hops": 2 }
+{ "u1_user_id": 3, "u2_user_id": 1, "fw_edge": [ 2, 3 ], "fw_hops": 2 }
+{ "u1_user_id": 3, "u2_user_id": 2, "fw_edge": [ 3 ], "fw_hops": 1 }
+{ "u1_user_id": 3, "u2_user_id": 4, "fw_edge": [ 4 ], "fw_hops": 1 }
+{ "u1_user_id": 3, "u2_user_id": 5, "fw_edge": [ 5, 4 ], "fw_hops": 2 }
+{ "u1_user_id": 4, "u2_user_id": 2, "fw_edge": [ 3, 4 ], "fw_hops": 2 }
+{ "u1_user_id": 4, "u2_user_id": 3, "fw_edge": [ 4 ], "fw_hops": 1 }
+{ "u1_user_id": 4, "u2_user_id": 5, "fw_edge": [ 5 ], "fw_hops": 1 }
+{ "u1_user_id": 4, "u2_user_id": 6, "fw_edge": [ 6, 5 ], "fw_hops": 2 }
+{ "u1_user_id": 5, "u2_user_id": 3, "fw_edge": [ 4, 5 ], "fw_hops": 2 }
+{ "u1_user_id": 5, "u2_user_id": 4, "fw_edge": [ 5 ], "fw_hops": 1 }
+{ "u1_user_id": 5, "u2_user_id": 6, "fw_edge": [ 6 ], "fw_hops": 1 }
+{ "u1_user_id": 6, "u2_user_id": 4, "fw_edge": [ 5, 6 ], "fw_hops": 2 }
+{ "u1_user_id": 6, "u2_user_id": 5, "fw_edge": [ 6 ], "fw_hops": 1 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/testsuite.xml b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
index 0ca8de6..a5ea9fc 100644
--- a/asterix-graphix/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
@@ -31,7 +31,7 @@
         <expected-error>Cannot drop dataverse: DATASET TestDataverse2.GenericDataset being used by GRAPH TestDataverse.TestGraph</expected-error>
         <expected-error>Bad definition for a graph element(.)*Cannot find dataset DatasetThatDoesNotExist in dataverse TestDataverse nor an alias with name DatasetThatDoesNotExist</expected-error>
         <expected-error>Bad definition for a graph element(.)*Cannot resolve ambiguous alias reference for identifier V</expected-error>
-        <expected-error>Conflicting primary keys for vertices with label Vertex1</expected-error>
+        <expected-error>Conflicting vertex label found: Vertex1</expected-error>
         <expected-error>Destination vertex Vertex3 not found in the edge EDGE_1</expected-error>
         <expected-error>Graph TestGraph already exists</expected-error>
         <expected-error>Graph GraphThatDoesntExist2 does not exist</expected-error>
@@ -42,9 +42,10 @@
     </test-case>
   </test-group>
   <test-group name="dangling-vertices">
-    <test-case FilePath="graphix">
+    <test-case FilePath="graphix" check-warnings="true">
       <compilation-unit name="dangling-vertices">
         <output-dir compare="Text">dangling-vertices</output-dir>
+        <expected-warn>Potential disconnected pattern encountered! A CROSS-JOIN has been introduced.</expected-warn>
       </compilation-unit>
     </test-case>
   </test-group>
@@ -70,7 +71,7 @@
     </test-case>
   </test-group>
   <test-group name="inference-resolution">
-    <test-case FilePath="graphix">
+    <test-case FilePath="graphix" check-warnings="true">
       <compilation-unit name="inference-resolution">
         <output-dir compare="Text">inference-resolution</output-dir>
       </compilation-unit>
@@ -83,16 +84,6 @@
       </compilation-unit>
     </test-case>
   </test-group>
-  <test-group name="minimal-resolution">
-    <test-case FilePath="graphix" check-warnings="true">
-      <compilation-unit name="minimal-resolution">
-        <output-dir compare="Text">minimal-resolution</output-dir>
-        <expected-warn>Vertex label could not be resolved. Assuming that vertex $n has all schema labels.</expected-warn>
-        <expected-warn>Vertex label could not be resolved. Assuming that vertex $m has all schema labels.</expected-warn>
-        <expected-warn>Vertex label could not be resolved. Assuming that vertex $n has all schema labels.</expected-warn>
-      </compilation-unit>
-    </test-case>
-  </test-group>
   <test-group name="on-query-error">
     <test-case FilePath="graphix">
       <compilation-unit name="on-query-error">
@@ -104,7 +95,7 @@
         <expected-error>Query edge given, but no edge is defined in the schema.</expected-error>
         <expected-error>Sub-path edges cannot have a hop length less than 1.</expected-error>
         <expected-error>Sub-path edges cannot have a maximum hop length (2) less than the minimum hop length (4).</expected-error>
-        <expected-error>Conflicting primary keys for vertices with label Vertex1</expected-error>
+        <expected-error>Conflicting vertex label found: Vertex1</expected-error>
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>
         <expected-error>Cannot resolve alias reference for undefined identifier invalidVariable</expected-error>