[NO-ISSUE][GRAPHIX] Adding a pure-rewrite version of Graphix.

Details:
- Users can create and delete managed graphs w/ CREATE GRAPH and DROP
  GRAPH. These will raise an error if a user tries to drop one of their
  dependents (and vice-versa).
- Users can introduce a set of variable bindings before UNNEST and JOIN
  clauses using the MATCH clause, which will iterate over all "matched"
  graph patterns. The MATCH clause also includes a "LEFT" variant.
- Graph edge patterns can be formulated as path finding queries, where a
  user can specify the range of hops between the two vertices of the edge
  pattern.
- Labels and directions can be inferred using labels and directions of
  vertices within the same FROM-GRAPH-CLAUSE. A naive evaluation
  strategy is used here (until we reach a fixed point).
- The initial set of Graphix functions are included.

Change-Id: I50f032ea4acc5ba46b86ae1052590a3e945c2497
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb-graph/+/16103
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Glenn Galvizo <ggalvizo@uci.edu>
diff --git a/asterix-graphix/pom.xml b/asterix-graphix/pom.xml
index c21c4ea..e1d517c 100644
--- a/asterix-graphix/pom.xml
+++ b/asterix-graphix/pom.xml
@@ -236,7 +236,7 @@
     <dependency>
       <groupId>org.apache.asterix</groupId>
       <artifactId>asterix-test-framework</artifactId>
-      <version>0.9.7-SNAPSHOT</version>
+      <version>${asterix.version}</version>
       <scope>test</scope>
     </dependency>
 
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
index 13d8c48..79a2b68 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/algebra/compiler/provider/GraphixCompilationProvider.java
@@ -18,16 +18,26 @@
  */
 package org.apache.asterix.graphix.algebra.compiler.provider;
 
+import java.util.Set;
+
 import org.apache.asterix.algebra.base.ILangExpressionToPlanTranslatorFactory;
 import org.apache.asterix.compiler.provider.IRuleSetFactory;
 import org.apache.asterix.compiler.provider.SqlppCompilationProvider;
 import org.apache.asterix.graphix.algebra.translator.GraphixExpressionToPlanTranslatorFactory;
 import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
 import org.apache.asterix.graphix.lang.rewrites.GraphixRewriterFactory;
+import org.apache.asterix.graphix.lang.rewrites.print.GraphixASTPrintVisitorFactory;
+import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IRewriterFactory;
 
 public class GraphixCompilationProvider extends SqlppCompilationProvider {
+    public static final String PRINT_REWRITE_METADATA_CONFIG = "graphix.print-rewrite";
+    public static final String RESOLVER_METADATA_CONFIG = "graphix.resolver";
+    public static final String RESOLVER_ITERATION_MAX_METADATA_CONFIG = "graphix.max-resolution-iterations";
+    public static final String MATCH_EVALUATION_METADATA_CONFIG = "graphix.match-evaluation";
+    public static final String EDGE_STRATEGY_METADATA_CONFIG = "graphix.edge-strategy";
+
     @Override
     public IParserFactory getParserFactory() {
         return new GraphixParserFactory();
@@ -44,7 +54,20 @@
     }
 
     @Override
+    public IAstPrintVisitorFactory getAstPrintVisitorFactory() {
+        return new GraphixASTPrintVisitorFactory();
+    }
+
+    @Override
     public ILangExpressionToPlanTranslatorFactory getExpressionToPlanTranslatorFactory() {
         return new GraphixExpressionToPlanTranslatorFactory();
     }
+
+    @Override
+    public Set<String> getCompilerOptions() {
+        Set<String> parentConfigurableParameters = super.getCompilerOptions();
+        parentConfigurableParameters.addAll(Set.of(PRINT_REWRITE_METADATA_CONFIG, MATCH_EVALUATION_METADATA_CONFIG,
+                RESOLVER_METADATA_CONFIG, RESOLVER_ITERATION_MAX_METADATA_CONFIG, EDGE_STRATEGY_METADATA_CONFIG));
+        return parentConfigurableParameters;
+    }
 }
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 59889a3..101fc06 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
@@ -18,11 +18,11 @@
  */
 package org.apache.asterix.graphix.app.translator;
 
-import java.util.Collections;
-import java.util.Iterator;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
 
 import org.apache.asterix.app.translator.QueryTranslator;
 import org.apache.asterix.common.api.IResponsePrinter;
@@ -33,30 +33,34 @@
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.compiler.provider.ILangCompilationProvider;
 import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
-import org.apache.asterix.graphix.lang.expression.GraphElementExpr;
+import org.apache.asterix.graphix.lang.expression.GraphElementBodyExpr;
 import org.apache.asterix.graphix.lang.rewrites.GraphixQueryRewriter;
 import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.graphix.metadata.entities.Graph;
+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;
+import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
+import org.apache.asterix.graphix.metadata.entity.dependency.ViewRequirements;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.lang.common.base.IStatementRewriter;
 import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
+import org.apache.asterix.lang.common.statement.CreateFunctionStatement;
+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.FunctionDropStatement;
 import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.common.statement.SynonymDropStatement;
 import org.apache.asterix.lang.common.statement.ViewDropStatement;
-import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.util.ExpressionUtils;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.declared.MetadataProvider;
-import org.apache.asterix.metadata.entities.DependencyKind;
 import org.apache.asterix.translator.IRequestParameters;
 import org.apache.asterix.translator.SessionOutput;
-import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.common.utils.Pair;
-import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 
 public class GraphixQueryTranslator extends QueryTranslator {
@@ -73,14 +77,128 @@
     public void setGraphElementNormalizedBody(MetadataProvider metadataProvider, GraphElementDecl graphElementDecl,
             GraphixQueryRewriter queryRewriter) throws CompilationException {
         // Create a query AST for our rewriter to walk through.
-        GraphElementExpr functionCall = new GraphElementExpr(graphElementDecl.getIdentifier());
+        GraphElementBodyExpr functionCall = new GraphElementBodyExpr(graphElementDecl.getIdentifier());
         Query query = ExpressionUtils.createWrappedQuery(functionCall, graphElementDecl.getSourceLocation());
 
-        // We call our rewriter to set the normalized bodies of {@code graphElementDecl}.
-        GraphixRewritingContext graphixRewritingContext =
-                new GraphixRewritingContext(metadataProvider, declaredFunctions, null,
-                        Collections.singletonList(graphElementDecl), warningCollector, query.getVarCounter());
-        queryRewriter.loadNormalizedGraphElements(graphixRewritingContext, query);
+        // We call our rewriter to set the normalized bodies of GRAPH-ELEMENT-DECL.
+        LangRewritingContext langRewritingContext = new LangRewritingContext(metadataProvider, declaredFunctions, null,
+                warningCollector, query.getVarCounter());
+        GraphixRewritingContext graphixRewritingContext = new GraphixRewritingContext(langRewritingContext);
+        queryRewriter.loadNormalizedGraphElement(graphixRewritingContext, query, graphElementDecl);
+    }
+
+    /**
+     * To create a view, we must perform the following:
+     * a) Check the view body for any named graphs.
+     * b) Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
+     * expressions will be recorded in the "Dataset" dataset.
+     * c) Record any graph-related dependencies for the view in our metadata.
+     */
+    @Override
+    protected CreateResult doCreateView(MetadataProvider metadataProvider, CreateViewStatement cvs,
+            DataverseName dataverseName, String viewName, DataverseName itemTypeDataverseName, String itemTypeName,
+            IStatementRewriter stmtRewriter, IRequestParameters requestParameters) throws Exception {
+        // Before executing our parent, analyze our view body for graph dependencies.
+        Set<DependencyIdentifier> graphDependencies = new HashSet<>();
+        GraphStatementHandlingUtil.collectDependenciesOnGraph(cvs.getViewBodyExpression(), dataverseName,
+                graphDependencies);
+
+        // Now execute the parent CREATE-VIEW function. Ensure that our VIEW is valid.
+        CreateResult createResult = super.doCreateView(metadataProvider, cvs, dataverseName, viewName,
+                itemTypeDataverseName, itemTypeName, stmtRewriter, requestParameters);
+        if (createResult == CreateResult.NOOP) {
+            return createResult;
+        }
+
+        // Our view is valid. Proceed by inserting / upserting a new dependence record.
+        GraphStatementHandlingUtil.acquireGraphExtensionWriteLocks(metadataProvider, dataverseName, viewName);
+        MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
+        metadataProvider.setMetadataTxnContext(mdTxnCtx);
+
+        // If we have an existing graph-dependency, fetch it first.
+        Optional<IEntityRequirements> existingRequirements = GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx)
+                .stream().filter(r -> r.getDataverseName().equals(dataverseName))
+                .filter(r -> r.getEntityName().equals(cvs.getViewName()))
+                .filter(r -> r.getDependentKind() == IEntityRequirements.DependentKind.VIEW).findFirst();
+        if (existingRequirements.isPresent() && !cvs.getReplaceIfExists()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, cvs.getSourceLocation(),
+                    "Graph dependency record for " + cvs.getViewName() + " already exists.");
+        }
+
+        // Insert / upsert into our GraphDependency dataset.
+        if (!graphDependencies.isEmpty()) {
+            ViewRequirements viewRequirements = new ViewRequirements(getActiveDataverseName(cvs.getDataverseName()),
+                    cvs.getViewName(), graphDependencies);
+            if (!existingRequirements.isPresent()) {
+                MetadataManager.INSTANCE.addEntity(mdTxnCtx, viewRequirements);
+
+            } else {
+                MetadataManager.INSTANCE.upsertEntity(mdTxnCtx, viewRequirements);
+            }
+        }
+        MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+
+        // Exit here. Our parent will release all locks.
+        return createResult;
+    }
+
+    /**
+     * To create a function, we must perform the following:
+     * a) Check the function body for any named graphs.
+     * b) Rewrite graph expressions into pure SQL++ expressions. The dependencies associated with the rewritten
+     * expressions will be recorded in the "Function" dataset.
+     * c) Record any graph-related dependencies for the function in our metadata.
+     */
+    @Override
+    protected CreateResult doCreateFunction(MetadataProvider metadataProvider, CreateFunctionStatement cfs,
+            FunctionSignature functionSignature, IStatementRewriter stmtRewriter, IRequestParameters requestParameters)
+            throws Exception {
+        // Before executing our parent, analyze our function body for graph dependencies.
+        Set<DependencyIdentifier> graphDependencies = new HashSet<>();
+        GraphStatementHandlingUtil.collectDependenciesOnGraph(cfs.getFunctionBodyExpression(),
+                cfs.getFunctionSignature().getDataverseName(), graphDependencies);
+
+        // Execute the parent CREATE-FUNCTION function. Ensure that our FUNCTION is valid.
+        CreateResult createResult =
+                super.doCreateFunction(metadataProvider, cfs, functionSignature, stmtRewriter, requestParameters);
+        if (createResult == CreateResult.NOOP) {
+            return createResult;
+        }
+
+        // Our function is valid. Proceed by inserting / upserting a new dependence record.
+        DataverseName dataverseName = functionSignature.getDataverseName();
+        GraphStatementHandlingUtil.acquireGraphExtensionWriteLocks(metadataProvider, dataverseName,
+                cfs.getFunctionSignature().toString());
+        MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
+        metadataProvider.setMetadataTxnContext(mdTxnCtx);
+
+        // If we have an existing requirement record, fetch it first.
+        DataverseName activeDataverseName = functionSignature.getDataverseName();
+        Optional<FunctionRequirements> existingRequirements = GraphixMetadataExtension
+                .getAllEntityRequirements(mdTxnCtx).stream()
+                .filter(r -> r.getDataverseName().equals(activeDataverseName))
+                .filter(r -> r.getDependentKind() == IEntityRequirements.DependentKind.FUNCTION)
+                .map(r -> (FunctionRequirements) r).filter(f -> f.getEntityName().equals(functionSignature.getName()))
+                .filter(f -> Integer.parseInt(f.getArityAsString()) == functionSignature.getArity()).findFirst();
+        if (existingRequirements.isPresent() && !cfs.getReplaceIfExists()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, cfs.getSourceLocation(),
+                    "Graph dependency record for " + functionSignature.getName() + " already exists.");
+        }
+
+        // Insert / upsert into our GraphDependency dataset.
+        if (!graphDependencies.isEmpty()) {
+            FunctionRequirements requirements = new FunctionRequirements(cfs.getFunctionSignature(), graphDependencies);
+            if (!existingRequirements.isPresent()) {
+                MetadataManager.INSTANCE.addEntity(mdTxnCtx, requirements);
+
+            } else {
+                MetadataManager.INSTANCE.upsertEntity(mdTxnCtx, requirements);
+            }
+        }
+        MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+
+        // Exit here. Our parent will release all locks.
+        return createResult;
     }
 
     @Override
@@ -89,17 +207,22 @@
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
 
         // Forbid dropping the synonym if any graphs depend on this synonym.
-        DataverseName workingDataverse = getActiveDataverseName(((SynonymDropStatement) stmt).getDataverseName());
-        String synonymName = ((SynonymDropStatement) stmt).getSynonymName();
-        throwErrorIfDependentExists(mdTxnCtx, workingDataverse,
-                (dependency) -> dependency.first == DependencyKind.SYNONYM
-                        && dependency.second.second.equals(synonymName));
+        SynonymDropStatement sds = (SynonymDropStatement) stmt;
+        DataverseName workingDataverse = getActiveDataverseName(sds.getDataverseName());
+        DependencyIdentifier dependencyIdentifier =
+                new DependencyIdentifier(workingDataverse, sds.getSynonymName(), DependencyIdentifier.Kind.SYNONYM);
+        GraphStatementHandlingUtil.throwIfDependentExists(mdTxnCtx, dependencyIdentifier);
 
         // Finish this transaction and perform the remainder of the DROP SYNONYM statement.
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
         super.handleDropSynonymStatement(metadataProvider, stmt);
     }
 
+    /**
+     * Before dropping a function, we perform the following:
+     * 1. Check if any of our existing graphs depend on the function to-be-dropped.
+     * 2. Remove the GraphDependency record for the function to-be-dropped if it exists.
+     */
     @Override
     protected void handleFunctionDropStatement(MetadataProvider metadataProvider, Statement stmt,
             IRequestParameters requestParameters) throws Exception {
@@ -109,27 +232,52 @@
         // Forbid dropping the function if any graphs depend on this function.
         FunctionSignature functionSignature = ((FunctionDropStatement) stmt).getFunctionSignature();
         DataverseName workingDataverse = getActiveDataverseName(functionSignature.getDataverseName());
-        throwErrorIfDependentExists(mdTxnCtx, workingDataverse,
-                (dependency) -> dependency.first == DependencyKind.FUNCTION
-                        && dependency.second.second.equals(functionSignature.getName())
-                        && dependency.second.third.equals(Integer.toString(functionSignature.getArity())));
+        DependencyIdentifier dependencyIdentifier =
+                new DependencyIdentifier(workingDataverse, functionSignature.getName(),
+                        String.valueOf(functionSignature.getArity()), DependencyIdentifier.Kind.FUNCTION);
+        GraphStatementHandlingUtil.throwIfDependentExists(mdTxnCtx, dependencyIdentifier);
+
+        // Drop the GraphDependency record associated with this function, if it exists.
+        Optional<FunctionRequirements> existingRequirements = GraphixMetadataExtension
+                .getAllEntityRequirements(mdTxnCtx).stream().filter(r -> r.getDataverseName().equals(workingDataverse))
+                .filter(r -> r.getEntityName().equals(functionSignature.getName()))
+                .filter(r -> r.getDependentKind() == IEntityRequirements.DependentKind.FUNCTION)
+                .map(r -> (FunctionRequirements) r)
+                .filter(f -> Integer.parseInt(f.getArityAsString()) == functionSignature.getArity()).findFirst();
+        if (existingRequirements.isPresent()) {
+            MetadataManager.INSTANCE.deleteEntity(mdTxnCtx, existingRequirements.get());
+        }
 
         // Finish this transaction and perform the remainder of the DROP FUNCTION statement.
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
         super.handleFunctionDropStatement(metadataProvider, stmt, requestParameters);
     }
 
+    /**
+     * Before dropping a view, we perform the following:
+     * 1. Check if any of our existing graphs depend on the view to-be-dropped.
+     * 2. Remove the GraphDependency record for the view to-be-dropped if it exists.
+     */
     @Override
     public void handleViewDropStatement(MetadataProvider metadataProvider, Statement stmt) throws Exception {
         MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
 
-        // Forbid dropping the dataset if any graphs depend on this dataset.
-        DataverseName workingDataverse = getActiveDataverseName(((ViewDropStatement) stmt).getDataverseName());
-        Identifier dsId = ((ViewDropStatement) stmt).getViewName();
-        throwErrorIfDependentExists(mdTxnCtx, workingDataverse,
-                (dependency) -> dependency.first == DependencyKind.DATASET
-                        && dependency.second.second.equals(dsId.getValue()));
+        // Forbid dropping the dataset if any graphs depend on this view (dataset).
+        ViewDropStatement vds = (ViewDropStatement) stmt;
+        DataverseName workingDataverse = getActiveDataverseName(vds.getDataverseName());
+        DependencyIdentifier dependencyIdentifier = new DependencyIdentifier(workingDataverse,
+                vds.getViewName().getValue(), DependencyIdentifier.Kind.DATASET);
+        GraphStatementHandlingUtil.throwIfDependentExists(mdTxnCtx, dependencyIdentifier);
+
+        // Drop the GraphDependency record associated with this view, if it exists.
+        Optional<IEntityRequirements> existingRequirements = GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx)
+                .stream().filter(r -> r.getDataverseName().equals(workingDataverse))
+                .filter(r -> r.getEntityName().equals(vds.getViewName().getValue()))
+                .filter(r -> r.getDependentKind() == IEntityRequirements.DependentKind.FUNCTION).findFirst();
+        if (existingRequirements.isPresent()) {
+            MetadataManager.INSTANCE.deleteEntity(mdTxnCtx, existingRequirements.get());
+        }
 
         // Finish this transaction and perform the remainder of the DROP VIEW statement.
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
@@ -143,70 +291,57 @@
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
 
         // Forbid dropping the dataset if any graphs depend on this dataset.
-        DataverseName workingDataverse = getActiveDataverseName(((DropDatasetStatement) stmt).getDataverseName());
-        Identifier dsId = ((DropDatasetStatement) stmt).getDatasetName();
-        throwErrorIfDependentExists(mdTxnCtx, workingDataverse,
-                (dependency) -> dependency.first == DependencyKind.DATASET
-                        && dependency.second.second.equals(dsId.getValue()));
+        DropDatasetStatement dds = (DropDatasetStatement) stmt;
+        DataverseName workingDataverse = getActiveDataverseName(dds.getDataverseName());
+        DependencyIdentifier dependencyIdentifier = new DependencyIdentifier(workingDataverse,
+                dds.getDatasetName().toString(), DependencyIdentifier.Kind.DATASET);
+        GraphStatementHandlingUtil.throwIfDependentExists(mdTxnCtx, dependencyIdentifier);
 
         // Finish this transaction and perform the remainder of the DROP DATASET statement.
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
         super.handleDatasetDropStatement(metadataProvider, stmt, hcc, requestParameters);
     }
 
-    private void throwErrorIfDependentExists(MetadataTransactionContext mdTxnCtx, DataverseName workingDataverse,
-            Function<Pair<DependencyKind, Triple<DataverseName, String, String>>, Boolean> isEntityDependent)
-            throws AlgebricksException {
-        List<Graph> allGraphs = GraphixMetadataExtension.getGraphs(mdTxnCtx, workingDataverse);
-        for (Graph graph : allGraphs) {
-            if (!graph.getDataverseName().equals(workingDataverse)) {
-                continue;
-            }
-            Iterator<Pair<DependencyKind, Triple<DataverseName, String, String>>> dependencyIterator =
-                    graph.getDependencies().getIterator();
-            while (dependencyIterator.hasNext()) {
-                Pair<DependencyKind, Triple<DataverseName, String, String>> dependency = dependencyIterator.next();
-                if (isEntityDependent.apply(dependency)) {
-                    throw new CompilationException(ErrorCode.CANNOT_DROP_OBJECT_DEPENDENT_EXISTS, dependency.first,
-                            dependency.first.getDependencyDisplayName(dependency.second), "graph",
-                            graph.getGraphName());
-                }
-            }
-        }
-    }
-
+    /**
+     * Before dropping a dataverse, we perform the following:
+     * 1. Check if any other entities outside the dataverse to-be-dropped depend on any entities inside the dataverse.
+     * 2. Remove all GraphDependency records associated with the dataverse to-be-dropped.
+     * 3. Remove all Graph records associated with the dataverse to-be-dropped.
+     */
     @Override
     protected void handleDataverseDropStatement(MetadataProvider metadataProvider, Statement stmt,
             IHyracksClientConnection hcc, IRequestParameters requestParameters) throws Exception {
         MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
 
-        // Forbid dropping this dataverse if other graphs that are outside this dataverse depend on this dataverse.
+        // Forbid dropping this dataverse if other entities that are outside this dataverse depend on this dataverse.
         DataverseName droppedDataverse = ((DataverseDropStatement) stmt).getDataverseName();
-        List<Graph> allGraphs = GraphixMetadataExtension.getGraphs(mdTxnCtx, null);
-        for (Graph graph : allGraphs) {
-            if (graph.getDataverseName().equals(droppedDataverse)) {
+        List<IEntityRequirements> requirementsList = GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx);
+        for (IEntityRequirements requirements : requirementsList) {
+            if (requirements.getDataverseName().equals(droppedDataverse)) {
                 continue;
             }
-            Iterator<Pair<DependencyKind, Triple<DataverseName, String, String>>> dependencyIterator =
-                    graph.getDependencies().getIterator();
-            while (dependencyIterator.hasNext()) {
-                Pair<DependencyKind, Triple<DataverseName, String, String>> dependency = dependencyIterator.next();
-                if (dependency.second.first.equals(droppedDataverse)) {
-                    throw new CompilationException(ErrorCode.CANNOT_DROP_DATAVERSE_DEPENDENT_EXISTS, dependency.first,
-                            dependency.first.getDependencyDisplayName(dependency.second), "graph",
-                            graph.getGraphName());
+            for (DependencyIdentifier dependency : requirements) {
+                if (dependency.getDataverseName().equals(droppedDataverse)) {
+                    throw new CompilationException(ErrorCode.CANNOT_DROP_DATAVERSE_DEPENDENT_EXISTS,
+                            dependency.getDependencyKind(), dependency.getDisplayName(),
+                            requirements.getDependentKind(), requirements.getDisplayName());
                 }
             }
         }
 
+        // Perform a drop for all GraphDependency records contained in this dataverse.
+        for (IEntityRequirements requirements : requirementsList) {
+            if (!requirements.getDataverseName().equals(droppedDataverse)) {
+                continue;
+            }
+            MetadataManager.INSTANCE.deleteEntity(mdTxnCtx, requirements);
+        }
+
         // Perform a drop for all graphs contained in this dataverse.
         MetadataProvider tempMdProvider = MetadataProvider.create(appCtx, metadataProvider.getDefaultDataverse());
         tempMdProvider.getConfig().putAll(metadataProvider.getConfig());
-        for (Graph graph : allGraphs) {
-            if (!graph.getDataverseName().equals(droppedDataverse)) {
-                continue;
-            }
+        for (Graph graph : GraphixMetadataExtension.getAllGraphs(mdTxnCtx, droppedDataverse)) {
             tempMdProvider.getLocks().reset();
             GraphDropStatement gds = new GraphDropStatement(droppedDataverse, graph.getGraphName(), false);
             gds.handle(hcc, this, requestParameters, tempMdProvider, 0);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
index fdd4ed4..f440c2f 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphElementIdentifier.java
@@ -21,16 +21,24 @@
 import java.io.Serializable;
 import java.util.Objects;
 
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * A unique identifier for a graph element (vertex or edge). A graph element is uniquely identified by:
+ * 1. The graph identifier associated with the graph element itself.
+ * 2. The kind of the element (vertex or edge).
+ * 3. The label associated with the element itself- a graph element has only one label in our user model.
+ */
 public class GraphElementIdentifier implements Serializable {
     private static final long serialVersionUID = 1L;
     private final GraphIdentifier graphIdentifier;
     private final Kind elementKind;
-    private final String labelName;
+    private final ElementLabel elementLabel;
 
-    public GraphElementIdentifier(GraphIdentifier graphIdentifier, Kind elementKind, String labelName) {
+    public GraphElementIdentifier(GraphIdentifier graphIdentifier, Kind elementKind, ElementLabel elementLabel) {
         this.graphIdentifier = graphIdentifier;
         this.elementKind = elementKind;
-        this.labelName = labelName;
+        this.elementLabel = elementLabel;
     }
 
     public GraphIdentifier getGraphIdentifier() {
@@ -41,13 +49,13 @@
         return elementKind;
     }
 
-    public String getLabelName() {
-        return labelName;
+    public ElementLabel getElementLabel() {
+        return elementLabel;
     }
 
     @Override
     public String toString() {
-        return graphIdentifier + "#" + labelName + " ( " + elementKind + " )";
+        return graphIdentifier + "#" + elementLabel + " ( " + elementKind + " )";
     }
 
     @Override
@@ -58,30 +66,18 @@
         if (o instanceof GraphElementIdentifier) {
             GraphElementIdentifier that = (GraphElementIdentifier) o;
             return graphIdentifier.equals(that.graphIdentifier) && elementKind.equals(that.elementKind)
-                    && labelName.equals(that.labelName);
+                    && elementLabel.equals(that.elementLabel);
         }
         return false;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(graphIdentifier, elementKind, labelName);
+        return Objects.hash(graphIdentifier, elementKind, elementLabel);
     }
 
     public enum Kind {
         VERTEX,
-        EDGE;
-
-        @Override
-        public String toString() {
-            switch (this) {
-                case EDGE:
-                    return "edge";
-                case VERTEX:
-                    return "vertex";
-                default:
-                    throw new IllegalStateException("Unknown graph element kind.");
-            }
-        }
+        EDGE
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
index 4b78434..c3cc5bd 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/common/metadata/GraphIdentifier.java
@@ -23,14 +23,19 @@
 
 import org.apache.asterix.common.metadata.DataverseName;
 
+/**
+ * A unique identifier for a graph. A graph is uniquely identified by:
+ * 1. The dataverse associated with the graph. A graph identifier must always belong to some dataverse.
+ * 2. The name of the graph. Anonymous graphs should have a name generated from its respective GRAPH-CONSTRUCTOR.
+ */
 public class GraphIdentifier implements Serializable {
     private static final long serialVersionUID = 1L;
     private final DataverseName dataverseName;
     private final String graphName;
 
     public GraphIdentifier(DataverseName dataverseName, String graphName) {
-        this.dataverseName = dataverseName;
-        this.graphName = graphName;
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.graphName = Objects.requireNonNull(graphName);
     }
 
     public DataverseName getDataverseName() {
@@ -53,7 +58,7 @@
         }
         if (o instanceof GraphIdentifier) {
             GraphIdentifier that = (GraphIdentifier) o;
-            return dataverseName.equals(that.dataverseName) && graphName.equals(that.graphName);
+            return dataverseName.equals(that.dataverseName) && Objects.equals(graphName, that.graphName);
         }
         return false;
     }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixMetadataExtension.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixMetadataExtension.java
index 68df8b4..e4894af 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixMetadataExtension.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/extension/GraphixMetadataExtension.java
@@ -19,15 +19,17 @@
 package org.apache.asterix.graphix.extension;
 
 import java.rmi.RemoteException;
-import java.util.Collections;
 import java.util.List;
 
 import org.apache.asterix.common.api.ExtensionId;
 import org.apache.asterix.common.exceptions.ACIDException;
 import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.metadata.bootstrap.GraphixMetadataIndexes;
-import org.apache.asterix.graphix.metadata.bootstrap.GraphixMetadataRecordTypes;
-import org.apache.asterix.graphix.metadata.entities.Graph;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixIndexDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.IGraphixIndexDetail;
+import org.apache.asterix.graphix.metadata.bootstrap.IRecordTypeDetail;
+import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.metadata.MetadataNode;
 import org.apache.asterix.metadata.MetadataTransactionContext;
@@ -52,12 +54,12 @@
 
     public static Graph getGraph(MetadataTransactionContext mdTxnCtx, DataverseName dataverseName, String graphName)
             throws AlgebricksException {
-        IExtensionMetadataSearchKey graphSearchKey = new IExtensionMetadataSearchKey() {
+        IExtensionMetadataSearchKey searchKey = new IExtensionMetadataSearchKey() {
             private static final long serialVersionUID = 1L;
 
             @Override
             public ExtensionMetadataDatasetId getDatasetId() {
-                return GraphixMetadataIndexes.GRAPH_METADATA_DATASET_EXTENSION_ID;
+                return GraphixIndexDetailProvider.getGraphIndexDetail().getExtensionDatasetID();
             }
 
             @Override
@@ -65,18 +67,18 @@
                 return MetadataNode.createTuple(dataverseName, graphName);
             }
         };
-        List<Graph> graphs = MetadataManager.INSTANCE.getEntities(mdTxnCtx, graphSearchKey);
+        List<Graph> graphs = MetadataManager.INSTANCE.getEntities(mdTxnCtx, searchKey);
         return (graphs.isEmpty()) ? null : graphs.get(0);
     }
 
-    public static List<Graph> getGraphs(MetadataTransactionContext mdTxnTtx, DataverseName dataverseName)
+    public static List<Graph> getAllGraphs(MetadataTransactionContext mdTxnTtx, DataverseName dataverseName)
             throws AlgebricksException {
-        IExtensionMetadataSearchKey graphDataverseSearchKey = new IExtensionMetadataSearchKey() {
+        IExtensionMetadataSearchKey dataverseSearchKey = new IExtensionMetadataSearchKey() {
             private static final long serialVersionUID = 1L;
 
             @Override
             public ExtensionMetadataDatasetId getDatasetId() {
-                return GraphixMetadataIndexes.GRAPH_METADATA_DATASET_EXTENSION_ID;
+                return GraphixIndexDetailProvider.getGraphIndexDetail().getExtensionDatasetID();
             }
 
             @Override
@@ -84,7 +86,25 @@
                 return (dataverseName == null) ? null : MetadataNode.createTuple(dataverseName);
             }
         };
-        return MetadataManager.INSTANCE.getEntities(mdTxnTtx, graphDataverseSearchKey);
+        return MetadataManager.INSTANCE.getEntities(mdTxnTtx, dataverseSearchKey);
+    }
+
+    public static List<IEntityRequirements> getAllEntityRequirements(MetadataTransactionContext mdTxnTtx)
+            throws AlgebricksException {
+        IExtensionMetadataSearchKey searchKey = new IExtensionMetadataSearchKey() {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public ExtensionMetadataDatasetId getDatasetId() {
+                return GraphixIndexDetailProvider.getGraphDependencyIndexDetail().getExtensionDatasetID();
+            }
+
+            @Override
+            public ITupleReference getSearchKey() {
+                return null;
+            }
+        };
+        return MetadataManager.INSTANCE.getEntities(mdTxnTtx, searchKey);
     }
 
     @Override
@@ -106,7 +126,8 @@
     @Override
     public List<ExtensionMetadataDataset> getExtensionIndexes() {
         try {
-            return Collections.singletonList(GraphixMetadataIndexes.GRAPH_DATASET);
+            return List.of(GraphixIndexDetailProvider.getGraphIndexDetail().getExtensionDataset(),
+                    GraphixIndexDetailProvider.getGraphDependencyIndexDetail().getExtensionDataset());
 
         } catch (Throwable th) {
             th.printStackTrace();
@@ -117,19 +138,29 @@
     @Override
     public void initializeMetadata(INCServiceContext appCtx)
             throws HyracksDataException, RemoteException, ACIDException {
-        MetadataBootstrap.enlistMetadataDataset(appCtx, GraphixMetadataIndexes.GRAPH_DATASET);
+        // Enlist our datasets.
+        IGraphixIndexDetail<?> graphIndexDetail = GraphixIndexDetailProvider.getGraphIndexDetail();
+        IGraphixIndexDetail<?> dependencyIndexDetail = GraphixIndexDetailProvider.getGraphDependencyIndexDetail();
+        MetadataBootstrap.enlistMetadataDataset(appCtx, graphIndexDetail.getExtensionDataset());
+        MetadataBootstrap.enlistMetadataDataset(appCtx, dependencyIndexDetail.getExtensionDataset());
+
+        // If this is a new universe, insert our extension datasets.
         if (MetadataBootstrap.isNewUniverse()) {
             MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
             try {
-                // Insert our sole new metadata dataset (Graph).
-                MetadataBootstrap.insertMetadataDatasets(mdTxnCtx,
-                        new IMetadataIndex[] { GraphixMetadataIndexes.GRAPH_DATASET });
+                // Insert our two new metadata datasets (Graph and GraphDependency).
+                MetadataBootstrap.insertMetadataDatasets(mdTxnCtx, new IMetadataIndex[] {
+                        graphIndexDetail.getExtensionDataset(), dependencyIndexDetail.getExtensionDataset() });
 
-                // Insert our sole datatype (Graph).
+                // Insert two new datatype (Graph and GraphDependency).
+                IRecordTypeDetail graphRecordDetail = GraphixRecordDetailProvider.getGraphRecordDetail();
+                IRecordTypeDetail dependencyRecordDetail = GraphixRecordDetailProvider.getGraphDependencyRecordDetail();
+                MetadataManager.INSTANCE.addDatatype(mdTxnCtx, new Datatype(MetadataConstants.METADATA_DATAVERSE_NAME,
+                        graphRecordDetail.getRecordType().getTypeName(), graphRecordDetail.getRecordType(), false));
                 MetadataManager.INSTANCE.addDatatype(mdTxnCtx,
                         new Datatype(MetadataConstants.METADATA_DATAVERSE_NAME,
-                                GraphixMetadataRecordTypes.GRAPH_RECORDTYPE.getTypeName(),
-                                GraphixMetadataRecordTypes.GRAPH_RECORDTYPE, false));
+                                dependencyRecordDetail.getRecordType().getTypeName(),
+                                dependencyRecordDetail.getRecordType(), false));
 
                 MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
 
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
new file mode 100644
index 0000000..0903a08
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/FunctionRewriteMap.java
@@ -0,0 +1,131 @@
+/*
+ * 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.EdgeVertexFunctionRewrite;
+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_SOURCE_VERTEX, new EdgeVertexFunctionRewrite(true));
+        graphixFunctionMap.put(GraphixFunctionIdentifiers.EDGE_DEST_VERTEX, new EdgeVertexFunctionRewrite(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
new file mode 100644
index 0000000..fcfa7bd
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionAliases.java
@@ -0,0 +1,63 @@
+/*
+ * 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 org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+public class GraphixFunctionAliases {
+    private static final Map<String, FunctionIdentifier> functionAliasMap;
+
+    static {
+        functionAliasMap = new HashMap<>();
+
+        // Build aliases for our vertex + edge functions.
+        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("edge-source-vertex", GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX);
+        functionAliasMap.put("source-vertex", GraphixFunctionIdentifiers.EDGE_SOURCE_VERTEX);
+        functionAliasMap.put("edge-dest-vertex", GraphixFunctionIdentifiers.EDGE_DEST_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);
+    }
+
+    /**
+     * @return {@link FunctionIdentifier} associated with the given function alias. Null otherwise.
+     */
+    public static FunctionIdentifier getFunctionIdentifier(String aliasName) {
+        return functionAliasMap.getOrDefault(aliasName, null);
+    }
+}
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
new file mode 100644
index 0000000..d743369
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionIdentifiers.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.function;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+public class GraphixFunctionIdentifiers {
+    private static final Map<String, FunctionIdentifier> functionIdentifierMap;
+
+    // Graphix functions should exist separate from the "ASTERIX_DV" dataverse.
+    public static final DataverseName GRAPHIX_DV = DataverseName.createBuiltinDataverseName("graphix");
+
+    /**
+     * @return {@link FunctionIdentifier} associated with the given function name. Null otherwise.
+     */
+    public static FunctionIdentifier getFunctionIdentifier(String functionName) {
+        return functionIdentifierMap.getOrDefault(functionName, null);
+    }
+
+    // Function to be used when we normalize a graph-element body.
+    public static final FunctionIdentifier GRAPH_ELEMENT_BODY =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "graph-element-body", 5);
+
+    // Functions that can be called on vertices and edges.
+    public static final FunctionIdentifier ELEMENT_LABEL =
+            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_SOURCE_VERTEX =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-source", 3);
+    public static final FunctionIdentifier EDGE_DEST_VERTEX =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "edge-dest", 3);
+
+    // Functions that can be called on paths.
+    public static final FunctionIdentifier PATH_HOP_COUNT =
+            new FunctionIdentifier(GRAPHIX_DV.getCanonicalForm(), "path-hop-count", 1);
+    public static final FunctionIdentifier PATH_VERTICES =
+            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.
+        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_SOURCE_VERTEX);
+        functionRegister.accept(EDGE_DEST_VERTEX);
+        functionRegister.accept(PATH_HOP_COUNT);
+        functionRegister.accept(PATH_VERTICES);
+        functionRegister.accept(PATH_EDGES);
+        functionRegister.accept(PATH_LABELS);
+    }
+}
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
new file mode 100644
index 0000000..60c1f21
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/GraphixFunctionResolver.java
@@ -0,0 +1,91 @@
+/*
+ * 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.Map;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+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.statement.FunctionDecl;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+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.
+ */
+public class GraphixFunctionResolver {
+    private final Map<FunctionSignature, FunctionDecl> declaredFunctionMap;
+    private final MetadataProvider metadataProvider;
+
+    public GraphixFunctionResolver(MetadataProvider metadataProvider,
+            Map<FunctionSignature, FunctionDecl> declaredFunctionMap) {
+        this.declaredFunctionMap = declaredFunctionMap;
+        this.metadataProvider = metadataProvider;
+    }
+
+    public FunctionSignature resolve(FunctionSignature functionSignature) throws CompilationException {
+        DataverseName workingDataverseName = functionSignature.getDataverseName();
+        if (workingDataverseName == null) {
+            workingDataverseName = metadataProvider.getDefaultDataverseName();
+        }
+
+        // Attempt to **find** if a user-defined function exists. We do not resolve these calls here.
+        if (!workingDataverseName.equals(FunctionConstants.ASTERIX_DV)
+                && !workingDataverseName.equals(FunctionConstants.ALGEBRICKS_DV)
+                && !workingDataverseName.equals(GraphixFunctionIdentifiers.GRAPHIX_DV)) {
+            FunctionDecl functionDecl;
+
+            // First, try resolve the call with the given number of arguments.
+            FunctionSignature signatureWithDataverse = functionSignature;
+            if (functionSignature.getDataverseName() == null) {
+                signatureWithDataverse = new FunctionSignature(workingDataverseName, functionSignature.getName(),
+                        functionSignature.getArity());
+            }
+            functionDecl = declaredFunctionMap.get(signatureWithDataverse);
+
+            // If this has failed, retry with a variable number of arguments.
+            if (functionDecl == null) {
+                FunctionSignature signatureWithVarArgs = new FunctionSignature(workingDataverseName,
+                        functionSignature.getName(), FunctionIdentifier.VARARGS);
+                functionDecl = declaredFunctionMap.get(signatureWithVarArgs);
+            }
+
+            if (functionDecl != null) {
+                return null;
+            }
+        }
+
+        // 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);
+        if (graphixFunctionIdentifier == null) {
+            graphixFunctionIdentifier = GraphixFunctionAliases.getFunctionIdentifier(functionName);
+        }
+        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;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeVertexFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeVertexFunctionRewrite.java
new file mode 100644
index 0000000..983c7f8
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/EdgeVertexFunctionRewrite.java
@@ -0,0 +1,76 @@
+/*
+ * 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 *_VERTEX(myEdgeVar, leftVertexVar, rightVertexVar), rewrite this function to return either
+ * vertex (depending on our owner).
+ * 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 EdgeVertexFunctionRewrite implements IFunctionRewrite {
+    private final boolean isSourceVertex;
+
+    public EdgeVertexFunctionRewrite(boolean isSourceVertex) {
+        this.isSourceVertex = isSourceVertex;
+    }
+
+    @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.EdgeType.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.EdgeType.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
new file mode 100644
index 0000000..7288aa7
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/IFunctionRewrite.java
@@ -0,0 +1,35 @@
+/*
+ * 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.function.FunctionRewriteMap;
+import org.apache.asterix.graphix.lang.rewrites.GraphixRewritingContext;
+import org.apache.asterix.lang.common.base.Expression;
+
+/**
+ * @see FunctionRewriteMap
+ */
+@FunctionalInterface
+public interface IFunctionRewrite {
+    Expression apply(GraphixRewritingContext graphixRewritingContext, List<Expression> callArguments)
+            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
new file mode 100644
index 0000000..b961000
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathEdgesFunctionRewrite.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.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/PathHopCountFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountFunctionRewrite.java
new file mode 100644
index 0000000..9b1801f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathHopCountFunctionRewrite.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.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/PathLabelsFunctionRewrite.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathLabelsFunctionRewrite.java
new file mode 100644
index 0000000..f998ce3
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathLabelsFunctionRewrite.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.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
new file mode 100644
index 0000000..4ffb338
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/function/rewrite/PathVerticesFunctionRewrite.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.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/lang/clause/FromGraphClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java
new file mode 100644
index 0000000..0e93470
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/FromGraphClause.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.clause;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+
+/**
+ * The logical starting AST node for Graphix queries. A FROM-GRAPH node includes the following:
+ * - Either a {@link GraphConstructor} OR a [dataverse, graph name] pair. The former indicates that we are dealing with
+ * an anonymous graph, while the latter indicates that we must search our metadata for the graph.
+ * - A list of {@link MatchClause} nodes, with a minimum size of one. The first MATCH node type must always be LEADING.
+ * - A list of {@link AbstractBinaryCorrelateClause} nodes, which may be empty. These include UNNEST and explicit JOINs.
+ */
+public class FromGraphClause extends AbstractClause {
+    // A FROM-MATCH must either have a graph constructor...
+    private final GraphConstructor graphConstructor;
+
+    // Or a reference to a named graph (both cannot be specified).
+    private final DataverseName dataverse;
+    private final Identifier name;
+
+    // Every FROM-MATCH **MUST** include at-least a single MATCH clause. Correlated clauses are optional.
+    private final List<MatchClause> matchClauses;
+    private final List<AbstractBinaryCorrelateClause> correlateClauses;
+
+    public FromGraphClause(DataverseName dataverse, Identifier name, List<MatchClause> matchClauses,
+            List<AbstractBinaryCorrelateClause> correlateClauses) {
+        this.graphConstructor = null;
+        this.dataverse = dataverse;
+        this.name = Objects.requireNonNull(name);
+        this.matchClauses = Objects.requireNonNull(matchClauses);
+        this.correlateClauses = Objects.requireNonNull(correlateClauses);
+
+        if (matchClauses.isEmpty()) {
+            throw new IllegalArgumentException("FROM-MATCH requires at least one MATCH clause.");
+        }
+    }
+
+    public FromGraphClause(GraphConstructor graphConstructor, List<MatchClause> matchClauses,
+            List<AbstractBinaryCorrelateClause> correlateClauses) {
+        this.graphConstructor = Objects.requireNonNull(graphConstructor);
+        this.dataverse = null;
+        this.name = null;
+        this.matchClauses = Objects.requireNonNull(matchClauses);
+        this.correlateClauses = Objects.requireNonNull(correlateClauses);
+
+        if (matchClauses.isEmpty()) {
+            throw new IllegalArgumentException("FROM-MATCH requires at least one MATCH clause.");
+        }
+    }
+
+    public GraphConstructor getGraphConstructor() {
+        return graphConstructor;
+    }
+
+    public DataverseName getDataverseName() {
+        return dataverse;
+    }
+
+    public Identifier getGraphName() {
+        return name;
+    }
+
+    public List<MatchClause> getMatchClauses() {
+        return matchClauses;
+    }
+
+    public List<AbstractBinaryCorrelateClause> getCorrelateClauses() {
+        return correlateClauses;
+    }
+
+    @Override
+    public ClauseType getClauseType() {
+        return null;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+
+    @Override
+    public String toString() {
+        return (graphConstructor == null) ? dataverse.toString() + "." + name : graphConstructor.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(graphConstructor, dataverse, name, matchClauses, correlateClauses);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof FromGraphClause)) {
+            return false;
+        }
+        FromGraphClause that = (FromGraphClause) object;
+        return Objects.equals(graphConstructor, that.graphConstructor) && Objects.equals(dataverse, that.dataverse)
+                && Objects.equals(name, that.name) && matchClauses.equals(that.matchClauses)
+                && Objects.equals(correlateClauses, that.correlateClauses);
+    }
+}
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
new file mode 100644
index 0000000..693ee70
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/GraphSelectBlock.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.clause;
+
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.clause.GroupbyClause;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.visitor.base.ISqlppVisitor;
+
+/**
+ * Starting AST node for a Graphix query, which will replace the FROM-CLAUSE with a {@link FromGraphClause} on
+ * parse. The goal of our Graphix rewriter is to replace these {@link FromGraphClause} nodes with applicable
+ * {@link org.apache.asterix.lang.sqlpp.clause.FromClause} nodes.
+ */
+public class GraphSelectBlock extends SelectBlock {
+    private FromGraphClause fromGraphClause;
+
+    public GraphSelectBlock(SelectClause selectClause, FromGraphClause fromGraphClause,
+            List<AbstractClause> letWhereClauses, GroupbyClause groupbyClause,
+            List<AbstractClause> letHavingClausesAfterGby) {
+        super(selectClause, null, letWhereClauses, groupbyClause, letHavingClausesAfterGby);
+        this.fromGraphClause = fromGraphClause;
+    }
+
+    public FromGraphClause getFromGraphClause() {
+        return fromGraphClause;
+    }
+
+    public void setFromGraphClause(FromGraphClause fromGraphClause) {
+        this.fromGraphClause = fromGraphClause;
+    }
+
+    public boolean hasFromGraphClause() {
+        return fromGraphClause != null;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        if (hasFromGraphClause()) {
+            return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+
+        } else {
+            return ((ISqlppVisitor<R, T>) visitor).visit(this, arg);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getFromClause(), getFromGraphClause(), getGroupbyClause(), getLetWhereList(),
+                getLetHavingListAfterGroupby(), getSelectClause());
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof GraphSelectBlock)) {
+            return false;
+        }
+        GraphSelectBlock target = (GraphSelectBlock) object;
+        return super.equals(target) && Objects.equals(getFromGraphClause(), target.getFromGraphClause());
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getSelectClause());
+        if (hasFromClause()) {
+            sb.append(' ').append(getFromClause());
+        } else if (hasFromGraphClause()) {
+            sb.append(' ').append(getFromGraphClause());
+        }
+        if (hasLetWhereClauses()) {
+            sb.append(' ').append(getLetWhereList());
+        }
+        if (hasGroupbyClause()) {
+            sb.append(' ').append(getGroupbyClause());
+        }
+        if (hasLetHavingClausesAfterGroupby()) {
+            sb.append(' ').append(getLetHavingListAfterGroupby());
+        }
+        return sb.toString();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java
new file mode 100644
index 0000000..e0fbf7f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/clause/MatchClause.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.clause;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.optype.MatchType;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+
+/**
+ * Container for a collection of {@link PathPatternExpr} nodes.
+ * - A MATCH node has three types: LEADING (indicating that this node is first), INNER (indicating that this node is not
+ * first, but all patterns must be matched), and LEFTOUTER (indicating that this node is optionally matched).
+ * - Under isomorphism semantics, two patterns in different MATCH nodes (one pattern in a LEADING MATCH node and
+ * one pattern in an INNER MATCH node) are equivalent to two patterns in a single LEADING MATCH node. See
+ * {@link org.apache.asterix.graphix.lang.rewrites.lower.assembly.IsomorphismLowerAssembly} for more detail.
+ */
+public class MatchClause extends AbstractClause {
+    private final List<PathPatternExpr> pathExpressions;
+    private final MatchType matchType;
+
+    public MatchClause(List<PathPatternExpr> pathExpressions, MatchType matchType) {
+        this.pathExpressions = Objects.requireNonNull(pathExpressions);
+        this.matchType = matchType;
+    }
+
+    public List<PathPatternExpr> getPathExpressions() {
+        return pathExpressions;
+    }
+
+    public MatchType getMatchType() {
+        return matchType;
+    }
+
+    @Override
+    public ClauseType getClauseType() {
+        return null;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+
+    @Override
+    public String toString() {
+        String pathString = pathExpressions.stream().map(PathPatternExpr::toString).collect(Collectors.joining("\n"));
+        return matchType.toString() + " " + pathString;
+
+    }
+}
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
new file mode 100644
index 0000000..d7d194f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/EdgePatternExpr.java
@@ -0,0 +1,97 @@
+/*
+ * 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.expression;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+
+/**
+ * A query edge (not to be confused with an edge constructor) is composed of a {@link EdgeDescriptor} (containing the
+ * edge labels, an optional edge variable, and the hop range), an list of optional internal {@link VertexPatternExpr}
+ * instances, a left {@link VertexPatternExpr}, and a right {@link VertexPatternExpr}.
+ */
+public class EdgePatternExpr extends AbstractExpression implements IGraphExpr {
+    private final List<VertexPatternExpr> internalVertices;
+    private final VertexPatternExpr leftVertex;
+    private final VertexPatternExpr rightVertex;
+    private final EdgeDescriptor edgeDescriptor;
+
+    public EdgePatternExpr(VertexPatternExpr leftVertex, VertexPatternExpr rightVertex, EdgeDescriptor edgeDescriptor) {
+        this.leftVertex = Objects.requireNonNull(leftVertex);
+        this.rightVertex = Objects.requireNonNull(rightVertex);
+        this.edgeDescriptor = Objects.requireNonNull(edgeDescriptor);
+        this.internalVertices = new ArrayList<>();
+
+        if (edgeDescriptor.getEdgeClass() == GraphExprKind.PATH_PATTERN) {
+            // If we have a sub-path, we have an internal vertex that we need to manage.
+            for (int i = 0; i < edgeDescriptor.getMaximumHops() - 1; i++) {
+                this.internalVertices.add(new VertexPatternExpr(null, new HashSet<>()));
+            }
+        }
+    }
+
+    public VertexPatternExpr getLeftVertex() {
+        return leftVertex;
+    }
+
+    public VertexPatternExpr getRightVertex() {
+        return rightVertex;
+    }
+
+    public EdgeDescriptor getEdgeDescriptor() {
+        return edgeDescriptor;
+    }
+
+    public List<VertexPatternExpr> getInternalVertices() {
+        return internalVertices;
+    }
+
+    public void replaceInternalVertices(List<VertexPatternExpr> internalVertices) {
+        this.internalVertices.clear();
+        this.internalVertices.addAll(internalVertices);
+    }
+
+    @Override
+    public String toString() {
+        return leftVertex.toString() + edgeDescriptor.toString() + rightVertex.toString();
+    }
+
+    @Override
+    public Kind getKind() {
+        return null;
+    }
+
+    @Override
+    public GraphExprKind getGraphExprKind() {
+        return edgeDescriptor.getEdgeClass();
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+}
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 8ba4cea..e06893a 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
@@ -19,29 +19,44 @@
 package org.apache.asterix.graphix.lang.expression;
 
 import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
 import org.apache.asterix.lang.common.base.AbstractExpression;
 import org.apache.asterix.lang.common.base.AbstractLangExpression;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
 
-public class GraphConstructor extends AbstractExpression {
-    private final List<VertexElement> vertexElements;
-    private final List<EdgeElement> edgeElements;
+/**
+ * An expression which describes the schema of a graph, containing a list of vertices ({@link VertexConstructor}) and
+ * a list of edges ({@link EdgeConstructor}) that connect the aforementioned vertices.
+ */
+public class GraphConstructor extends AbstractExpression implements IGraphExpr {
+    private final List<VertexConstructor> vertexConstructors;
+    private final List<EdgeConstructor> edgeConstructors;
 
-    public GraphConstructor(List<VertexElement> vertexElements, List<EdgeElement> edgeElements) {
-        this.vertexElements = vertexElements;
-        this.edgeElements = edgeElements;
+    // On parsing, we want to identify which graph-constructors are unique from one another.
+    private final UUID instanceID;
+
+    public GraphConstructor(List<VertexConstructor> vertexConstructors, List<EdgeConstructor> edgeConstructors) {
+        this.vertexConstructors = vertexConstructors;
+        this.edgeConstructors = edgeConstructors;
+        this.instanceID = UUID.randomUUID();
     }
 
-    public List<VertexElement> getVertexElements() {
-        return vertexElements;
+    public List<VertexConstructor> getVertexElements() {
+        return vertexConstructors;
     }
 
-    public List<EdgeElement> getEdgeElements() {
-        return edgeElements;
+    public List<EdgeConstructor> getEdgeElements() {
+        return edgeConstructors;
+    }
+
+    public String getInstanceID() {
+        return instanceID.toString();
     }
 
     @Override
@@ -50,11 +65,21 @@
     }
 
     @Override
+    public GraphExprKind getGraphExprKind() {
+        return GraphExprKind.GRAPH_CONSTRUCTOR;
+    }
+
+    @Override
     public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
         return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(vertexConstructors, edgeConstructors);
+    }
+
+    @Override
     public boolean equals(Object object) {
         if (this == object) {
             return true;
@@ -63,17 +88,24 @@
             return false;
         }
         GraphConstructor that = (GraphConstructor) object;
-        return vertexElements.equals(that.vertexElements) && edgeElements.equals(that.edgeElements);
+        return vertexConstructors.equals(that.vertexConstructors) && edgeConstructors.equals(that.edgeConstructors);
     }
 
-    public static class VertexElement extends AbstractLangExpression {
+    /**
+     * 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.
+     */
+    public static class VertexConstructor extends AbstractLangExpression {
         private final List<Integer> primaryKeySourceIndicators;
         private final List<List<String>> primaryKeyFields;
         private final Expression expression;
+        private final ElementLabel label;
         private final String definition;
-        private final String label;
 
-        public VertexElement(String label, List<List<String>> primaryKeyFields,
+        public VertexConstructor(ElementLabel label, List<List<String>> primaryKeyFields,
                 List<Integer> primaryKeySourceIndicators, Expression expression, String definition) {
             this.primaryKeySourceIndicators = primaryKeySourceIndicators;
             this.primaryKeyFields = primaryKeyFields;
@@ -98,7 +130,7 @@
             return definition;
         }
 
-        public String getLabel() {
+        public ElementLabel getLabel() {
             return label;
         }
 
@@ -113,44 +145,55 @@
         }
 
         @Override
+        public int hashCode() {
+            return Objects.hash(primaryKeyFields, expression, definition, label);
+        }
+
+        @Override
         public boolean equals(Object object) {
             if (this == object) {
                 return true;
             }
-            if (!(object instanceof VertexElement)) {
+            if (!(object instanceof VertexConstructor)) {
                 return false;
             }
-            VertexElement that = (VertexElement) object;
+            VertexConstructor that = (VertexConstructor) object;
             return primaryKeySourceIndicators.equals(that.primaryKeySourceIndicators)
                     && primaryKeyFields.equals(that.primaryKeyFields) && expression.equals(that.expression)
                     && definition.equals(that.definition) && label.equals(that.label);
         }
     }
 
-    public static class EdgeElement extends AbstractLangExpression {
+    /**
+     * 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 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.
+     */
+    public static class EdgeConstructor extends AbstractLangExpression {
         private final List<Integer> destinationKeySourceIndicators;
         private final List<Integer> sourceKeySourceIndicators;
-        private final List<Integer> primaryKeySourceIndicators;
 
         private final List<List<String>> destinationKeyFields;
         private final List<List<String>> sourceKeyFields;
-        private final List<List<String>> primaryKeyFields;
 
-        private final String destinationLabel, edgeLabel, sourceLabel;
+        private final ElementLabel destinationLabel, edgeLabel, sourceLabel;
         private final Expression expression;
         private final String definition;
 
-        public EdgeElement(String edgeLabel, String destinationLabel, String sourceLabel,
-                List<List<String>> primaryKeyFields, List<Integer> primaryKeySourceIndicators,
+        public EdgeConstructor(ElementLabel edgeLabel, ElementLabel destinationLabel, ElementLabel sourceLabel,
                 List<List<String>> destinationKeyFields, List<Integer> destinationKeySourceIndicators,
                 List<List<String>> sourceKeyFields, List<Integer> sourceKeySourceIndicators, Expression expression,
                 String definition) {
             this.destinationKeySourceIndicators = destinationKeySourceIndicators;
             this.sourceKeySourceIndicators = sourceKeySourceIndicators;
-            this.primaryKeySourceIndicators = primaryKeySourceIndicators;
             this.destinationKeyFields = destinationKeyFields;
             this.sourceKeyFields = sourceKeyFields;
-            this.primaryKeyFields = primaryKeyFields;
             this.destinationLabel = destinationLabel;
             this.edgeLabel = edgeLabel;
             this.sourceLabel = sourceLabel;
@@ -166,10 +209,6 @@
             return sourceKeySourceIndicators;
         }
 
-        public List<Integer> getPrimaryKeySourceIndicators() {
-            return primaryKeySourceIndicators;
-        }
-
         public List<List<String>> getDestinationKeyFields() {
             return destinationKeyFields;
         }
@@ -178,19 +217,15 @@
             return sourceKeyFields;
         }
 
-        public List<List<String>> getPrimaryKeyFields() {
-            return primaryKeyFields;
-        }
-
-        public String getDestinationLabel() {
+        public ElementLabel getDestinationLabel() {
             return destinationLabel;
         }
 
-        public String getEdgeLabel() {
+        public ElementLabel getEdgeLabel() {
             return edgeLabel;
         }
 
-        public String getSourceLabel() {
+        public ElementLabel getSourceLabel() {
             return sourceLabel;
         }
 
@@ -208,7 +243,7 @@
             String sourceNodePattern = "(:" + sourceLabel + ")";
             String destinationNodePattern = "(:" + destinationLabel + ")";
             String edgePattern = sourceNodePattern + "-" + edgeBodyPattern + "->" + destinationNodePattern;
-            return (definition == null) ? edgePattern : (edgePattern + " AS " + definition);
+            return edgePattern + " AS " + definition;
         }
 
         @Override
@@ -217,22 +252,26 @@
         }
 
         @Override
+        public int hashCode() {
+            return Objects.hash(destinationKeyFields, sourceKeyFields, destinationLabel, edgeLabel, sourceLabel,
+                    expression, definition);
+        }
+
+        @Override
         public boolean equals(Object object) {
             if (this == object) {
                 return true;
             }
-            if (!(object instanceof EdgeElement)) {
+            if (!(object instanceof EdgeConstructor)) {
                 return false;
             }
-            EdgeElement that = (EdgeElement) object;
+            EdgeConstructor that = (EdgeConstructor) object;
             return destinationKeySourceIndicators.equals(that.destinationKeySourceIndicators)
                     && sourceKeySourceIndicators.equals(that.sourceKeySourceIndicators)
-                    && primaryKeySourceIndicators.equals(that.primaryKeySourceIndicators)
                     && destinationKeyFields.equals(that.destinationKeyFields)
-                    && sourceKeyFields.equals(that.sourceKeyFields) && primaryKeyFields.equals(that.primaryKeyFields)
-                    && destinationLabel.equals(that.destinationLabel) && edgeLabel.equals(that.edgeLabel)
-                    && sourceLabel.equals(that.sourceLabel) && expression.equals(that.expression)
-                    && definition.equals(that.definition);
+                    && sourceKeyFields.equals(that.sourceKeyFields) && destinationLabel.equals(that.destinationLabel)
+                    && edgeLabel.equals(that.edgeLabel) && sourceLabel.equals(that.sourceLabel)
+                    && expression.equals(that.expression) && definition.equals(that.definition);
         }
     }
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementBodyExpr.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementBodyExpr.java
new file mode 100644
index 0000000..bb711f9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementBodyExpr.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.expression;
+
+import java.util.Collections;
+
+import org.apache.asterix.common.functions.FunctionSignature;
+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.lang.common.expression.CallExpr;
+
+public class GraphElementBodyExpr extends CallExpr implements IGraphExpr {
+    // A graph element is uniquely identified by its identifier.
+    private final GraphElementIdentifier identifier;
+
+    public GraphElementBodyExpr(GraphElementIdentifier identifier) {
+        super(new FunctionSignature(GraphixFunctionIdentifiers.GRAPH_ELEMENT_BODY), Collections.emptyList(), null);
+        this.identifier = identifier;
+    }
+
+    public GraphElementIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    public GraphIdentifier getGraphIdentifier() {
+        return identifier.getGraphIdentifier();
+    }
+
+    @Override
+    public Kind getKind() {
+        return null;
+    }
+
+    @Override
+    public GraphExprKind getGraphExprKind() {
+        return GraphExprKind.GRAPH_ELEMENT_BODY;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementExpr.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementExpr.java
deleted file mode 100644
index 11c1e34..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/GraphElementExpr.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.asterix.graphix.lang.expression;
-
-import java.util.Collections;
-
-import org.apache.asterix.common.functions.FunctionConstants;
-import org.apache.asterix.common.functions.FunctionSignature;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.lang.common.expression.CallExpr;
-import org.apache.asterix.lang.common.statement.Query;
-import org.apache.asterix.lang.common.util.ExpressionUtils;
-import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
-
-public class GraphElementExpr extends CallExpr {
-    public static final String GRAPH_ELEMENT_FUNCTION_NAME = "graph-element";
-    public static final FunctionIdentifier GRAPH_ELEMENT_FUNCTION_ID =
-            new FunctionIdentifier(FunctionConstants.ASTERIX_NS, GRAPH_ELEMENT_FUNCTION_NAME, 5);
-
-    // A graph element is uniquely identified by its identifier.
-    private final GraphElementIdentifier identifier;
-
-    public GraphElementExpr(GraphElementIdentifier identifier) {
-        super(new FunctionSignature(GRAPH_ELEMENT_FUNCTION_ID), Collections.emptyList(), null);
-        this.identifier = identifier;
-    }
-
-    public GraphElementIdentifier getIdentifier() {
-        return identifier;
-    }
-
-    public GraphIdentifier getGraphIdentifier() {
-        return identifier.getGraphIdentifier();
-    }
-
-    public static Query createGraphElementAccessorQuery(GraphElementDecl graphElementDecl) {
-        GraphElementExpr functionCall = new GraphElementExpr(graphElementDecl.getIdentifier());
-        return ExpressionUtils.createWrappedQuery(functionCall, graphElementDecl.getSourceLocation());
-    }
-}
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/IGraphExpr.java
similarity index 68%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/IGraphExpr.java
index 9667fd6..18440d7 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/IGraphExpr.java
@@ -16,7 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.expression;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+import org.apache.asterix.lang.common.base.ILangExpression;
+
+public interface IGraphExpr extends ILangExpression {
+    GraphExprKind getGraphExprKind();
+
+    enum GraphExprKind {
+        GRAPH_CONSTRUCTOR,
+        GRAPH_ELEMENT_BODY,
+
+        EDGE_PATTERN,
+        VERTEX_PATTERN,
+        PATH_PATTERN
+    }
+}
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
new file mode 100644
index 0000000..0091ec7
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/PathPatternExpr.java
@@ -0,0 +1,76 @@
+/*
+ * 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.expression;
+
+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.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.
+ */
+public class PathPatternExpr extends AbstractExpression implements IGraphExpr {
+    private final List<VertexPatternExpr> vertexExpressions;
+    private final List<EdgePatternExpr> edgeExpressions;
+    private VariableExpr variableExpr;
+
+    public PathPatternExpr(List<VertexPatternExpr> vertexExpressions, List<EdgePatternExpr> edgeExpressions,
+            VariableExpr variableExpr) {
+        this.vertexExpressions = Objects.requireNonNull(vertexExpressions);
+        this.edgeExpressions = Objects.requireNonNull(edgeExpressions);
+        this.variableExpr = variableExpr;
+    }
+
+    public List<VertexPatternExpr> getVertexExpressions() {
+        return vertexExpressions;
+    }
+
+    public List<EdgePatternExpr> getEdgeExpressions() {
+        return edgeExpressions;
+    }
+
+    public VariableExpr getVariableExpr() {
+        return variableExpr;
+    }
+
+    public void setVariableExpr(VariableExpr variableExpr) {
+        this.variableExpr = variableExpr;
+    }
+
+    @Override
+    public Kind getKind() {
+        return null;
+    }
+
+    @Override
+    public GraphExprKind getGraphExprKind() {
+        return GraphExprKind.PATH_PATTERN;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+}
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
new file mode 100644
index 0000000..a9b4ca6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/expression/VertexPatternExpr.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.expression;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractExpression;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+
+/**
+ * A query vertex (not to be confused with a vertex constructor) is composed of a set of labels (which may be empty)
+ * and a variable (which may initially be null).
+ */
+public class VertexPatternExpr extends AbstractExpression implements IGraphExpr {
+    private final Set<ElementLabel> labels;
+    private VariableExpr variableExpr;
+
+    public VertexPatternExpr(VariableExpr variableExpr, Set<ElementLabel> labels) {
+        this.variableExpr = variableExpr;
+        this.labels = labels;
+    }
+
+    public Set<ElementLabel> getLabels() {
+        return labels;
+    }
+
+    public VariableExpr getVariableExpr() {
+        return variableExpr;
+    }
+
+    public void setVariableExpr(VariableExpr variableExpr) {
+        this.variableExpr = variableExpr;
+    }
+
+    public List<GraphElementIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
+        return labels.stream()
+                .map(v -> new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.VERTEX, v))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public String toString() {
+        String labelsString = labels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
+        String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
+        return String.format("(%s:%s)", variableString, labelsString);
+    }
+
+    @Override
+    public Kind getKind() {
+        return null;
+    }
+
+    @Override
+    public GraphExprKind getGraphExprKind() {
+        return GraphExprKind.VERTEX_PATTERN;
+    }
+
+    @Override
+    public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+        return ((IGraphixLangVisitor<R, T>) visitor).visit(this, arg);
+    }
+}
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/optype/MatchType.java
similarity index 81%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/optype/MatchType.java
index 9667fd6..1e75f3f 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/optype/MatchType.java
@@ -16,7 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.optype;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+/**
+ * @see org.apache.asterix.graphix.lang.clause.MatchClause
+ */
+public enum MatchType {
+    LEADING,
+    INNER,
+    LEFTOUTER
+}
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 b1337b7..c4634e0 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
@@ -19,24 +19,29 @@
 package org.apache.asterix.graphix.lang.parser;
 
 import java.io.StringReader;
-import java.util.Objects;
 
 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.entities.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Element;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
 
 public final class GraphElementBodyParser {
     // Just a wrapper for the parseGraphElementBody method.
-    public static GraphElementDecl parse(Graph.Element element, GraphixParserFactory parserFactory,
+    public static GraphElementDecl parse(Element element, GraphixParserFactory parserFactory,
             IWarningCollector warningCollector) throws CompilationException {
         GraphElementDecl graphElementDecl = null;
-        for (String definition : element.getDefinitions()) {
-            if (Objects.equals(definition, "")) {
-                continue;
-            }
+        for (String definition : element.getDefinitionBodies()) {
+            // Parse our the definition.
             GraphixParser parser = (GraphixParser) parserFactory.createParser(new StringReader(definition));
-            GraphElementDecl parsedElementDecl = parser.parseGraphElementBody(element.getIdentifier());
+            GraphElementDecl 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) {
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 9e88251..0348eb8 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
@@ -18,66 +18,120 @@
  */
 package org.apache.asterix.graphix.lang.rewrites;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayDeque;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 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.extension.GraphixMetadataExtension;
-import org.apache.asterix.graphix.lang.expression.GraphElementExpr;
-import org.apache.asterix.graphix.lang.parser.GraphElementBodyParser;
+import org.apache.asterix.graphix.function.GraphixFunctionIdentifiers;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.expression.GraphElementBodyExpr;
 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.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.GraphixFunctionCallVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.GraphixLoweringVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.PostRewriteCheckVisitor;
+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.metadata.entities.Graph;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IReturningStatement;
 import org.apache.asterix.lang.common.expression.AbstractCallExpression;
+import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.statement.Query;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.util.ExpressionUtils;
+import org.apache.asterix.lang.sqlpp.rewrites.SqlppFunctionBodyRewriter;
 import org.apache.asterix.lang.sqlpp.rewrites.SqlppQueryRewriter;
 import org.apache.asterix.lang.sqlpp.rewrites.visitor.SqlppGatherFunctionCallsVisitor;
+import org.apache.asterix.metadata.declared.MetadataProvider;
 import org.apache.asterix.metadata.entities.Dataverse;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 import org.apache.hyracks.api.exceptions.SourceLocation;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
 
+/**
+ * Rewriter for Graphix queries, which will lower all graph AST nodes into SQL++ AST nodes. We perform the following:
+ * 1. Perform an error-checking on the fresh AST (immediately after parsing).
+ * 2. Populate the unknowns in our AST (e.g. vertex / edge variables, projections, GROUP-BY keys).
+ * 3. Perform a variable-scoping pass to identify illegal variables (either duplicate or out-of-scope).
+ * 4. For the remainder of our Graphix function calls, rewrite each call into a subtree w/o Graphix functions.
+ * 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.
+ */
 public class GraphixQueryRewriter extends SqlppQueryRewriter {
+    private static final Logger LOGGER = LogManager.getLogger(GraphixQueryRewriter.class);
+
     private final GraphixParserFactory parserFactory;
     private final SqlppQueryRewriter bodyRewriter;
 
     public GraphixQueryRewriter(IParserFactory parserFactory) {
         super(parserFactory);
-
-        // We can safely downcast to our specific parser factory here.
         this.parserFactory = (GraphixParserFactory) parserFactory;
-        this.bodyRewriter = new SqlppQueryRewriter(parserFactory);
+        this.bodyRewriter = getFunctionAndViewBodyRewriter();
     }
 
-    public void rewrite(GraphixRewritingContext rewriteContext, IReturningStatement topStatement,
+    @Override
+    public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
             boolean allowNonStoredUdfCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
             throws CompilationException {
-        // Get the graph elements in the top statement.
-        Map<GraphElementIdentifier, GraphElementDecl> graphElements =
-                loadNormalizedGraphElements(rewriteContext, topStatement);
+        LOGGER.debug("Starting Graphix AST rewrites.");
 
-        // Perform the remainder of our rewrites in our parent.
-        super.rewrite(rewriteContext.getLangRewritingContext(), topStatement, allowNonStoredUdfCalls,
-                inlineUdfsAndViews, externalVars);
+        // 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);
+        topStatement.getBody().accept(preRewriteCheckVisitor, null);
+
+        // Perform the Graphix rewrites.
+        rewriteGraphixASTNodes(langRewritingContext, topStatement);
+
+        // 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).");
+        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.");
+
+        // If desired, log the SQL++ query equivalent (i.e. turn the AST back into a query and log this).
+        MetadataProvider metadataProvider = langRewritingContext.getMetadataProvider();
+        String printRewriteMetadataKeyName = GraphixCompilationProvider.PRINT_REWRITE_METADATA_CONFIG;
+        String printRewriteOption = (String) metadataProvider.getConfig().get(printRewriteMetadataKeyName);
+        if ((printRewriteOption != null && printRewriteOption.equalsIgnoreCase("true")) || LOGGER.isTraceEnabled()) {
+            StringWriter stringWriter = new StringWriter();
+            PrintWriter printWriter = new PrintWriter(stringWriter);
+            new SqlppASTPrintQueryVisitor(printWriter).visit((Query) topStatement, null);
+            LOGGER.log(LOGGER.getLevel(), "Rewritten Graphix query: " + stringWriter);
+        }
     }
 
-    public Map<GraphElementIdentifier, GraphElementDecl> loadNormalizedGraphElements(
-            GraphixRewritingContext rewriteContext, IReturningStatement topExpr) throws CompilationException {
-        Map<GraphElementIdentifier, GraphElementDecl> graphElements = new HashMap<>();
-
+    public void loadNormalizedGraphElement(GraphixRewritingContext graphixRewritingContext, IReturningStatement topExpr,
+            GraphElementDecl graphElementDecl) throws CompilationException {
         // Gather all function calls.
         Deque<AbstractCallExpression> workQueue = new ArrayDeque<>();
         SqlppGatherFunctionCallsVisitor callVisitor = new SqlppGatherFunctionCallsVisitor(workQueue);
@@ -87,71 +141,114 @@
 
         AbstractCallExpression fnCall;
         while ((fnCall = workQueue.poll()) != null) {
-            // Load only the graph element declarations (we will load the rest of the elements in the parent).
-            if (!fnCall.getKind().equals(Expression.Kind.CALL_EXPRESSION)
-                    || !fnCall.getFunctionSignature().getName().equals(GraphElementExpr.GRAPH_ELEMENT_FUNCTION_NAME)) {
+            // We only care about graph element declarations.
+            FunctionSignature functionSignature = fnCall.getFunctionSignature();
+            if (!functionSignature.getName().equals(GraphixFunctionIdentifiers.GRAPH_ELEMENT_BODY.getName())
+                    || !Objects.equals(functionSignature.getDataverseName(), GraphixFunctionIdentifiers.GRAPHIX_DV)) {
                 continue;
             }
-            GraphElementExpr graphElementExpr = (GraphElementExpr) fnCall;
-            GraphElementIdentifier identifier = graphElementExpr.getIdentifier();
-            if (!graphElements.containsKey(identifier)) {
 
-                // First, check if we have already loaded this graph element.
-                GraphElementDecl elementDecl = rewriteContext.getDeclaredGraphElements().get(identifier);
-
-                // If we cannot find the graph element in our context, search our metadata.
-                if (elementDecl == null) {
-                    GraphIdentifier graphIdentifier = identifier.getGraphIdentifier();
-                    Graph graph;
-                    try {
-                        graph = GraphixMetadataExtension.getGraph(
-                                rewriteContext.getMetadataProvider().getMetadataTxnContext(),
-                                graphIdentifier.getDataverseName(), graphIdentifier.getGraphName());
-
-                    } catch (AlgebricksException e) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR,
-                                graphElementExpr.getSourceLocation(),
-                                "Graph " + graphIdentifier.getGraphName() + " does not exist.");
-                    }
-
-                    // Parse our graph element.
-                    if (graph == null) {
-                        throw new CompilationException(ErrorCode.COMPILATION_ERROR,
-                                graphElementExpr.getSourceLocation(),
-                                "Graph " + graphIdentifier.getGraphName() + " does not exist.");
-                    }
-                    Graph.Element element = graph.getGraphSchema().getElement(identifier);
-                    elementDecl = GraphElementBodyParser.parse(element, parserFactory,
-                            rewriteContext.getLangRewritingContext().getWarningCollector());
-                }
-
-                // Get our normalized element bodies.
-                List<Expression> normalizedBodies = elementDecl.getNormalizedBodies();
-                if (normalizedBodies.size() != elementDecl.getBodies().size()) {
-                    GraphIdentifier graphIdentifier = elementDecl.getGraphIdentifier();
-                    for (Expression body : elementDecl.getBodies()) {
-                        Expression normalizedBody =
-                                rewriteGraphElementBody(rewriteContext, graphIdentifier.getDataverseName(), body,
-                                        Collections.emptyList(), elementDecl.getSourceLocation());
-                        normalizedBodies.add(normalizedBody);
-                    }
-                }
-
-                // Store the element declaration in our map, and iterate over this body.
-                graphElements.put(identifier, elementDecl);
-                for (Expression e : elementDecl.getNormalizedBodies()) {
-                    e.accept(callVisitor, null);
+            // Get our normalized element bodies, by calling our rewriter.
+            List<Expression> normalizedBodies = graphElementDecl.getNormalizedBodies();
+            if (normalizedBodies.size() != graphElementDecl.getBodies().size()) {
+                GraphIdentifier graphIdentifier = graphElementDecl.getGraphIdentifier();
+                for (Expression body : graphElementDecl.getBodies()) {
+                    Expression normalizedBody =
+                            rewriteGraphElementBody(graphixRewritingContext, graphIdentifier.getDataverseName(), body,
+                                    Collections.emptyList(), graphElementDecl.getSourceLocation());
+                    normalizedBodies.add(normalizedBody);
                 }
             }
-        }
 
-        return graphElements;
+            // We should only have one element. We can safely break here.
+            break;
+        }
     }
 
-    private Expression rewriteGraphElementBody(GraphixRewritingContext rewriteContext, DataverseName elementDataverse,
-            Expression bodyExpr, List<VarIdentifier> externalVars, SourceLocation sourceLoc)
+    public void rewriteGraphixASTNodes(LangRewritingContext langRewritingContext, IReturningStatement topStatement)
             throws CompilationException {
-        Dataverse defaultDataverse = rewriteContext.getMetadataProvider().getDefaultDataverse();
+        // 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);
+
+        // Verify that variables are properly within scope.
+        LOGGER.trace("Verifying that variables are unique and are properly scoped.");
+        ScopingCheckVisitor scopingCheckVisitor = new ScopingCheckVisitor(langRewritingContext);
+        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);
+
+        // 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);
+
+        // 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<>();
+        ElementLookupTableVisitor elementLookupTableVisitor =
+                new ElementLookupTableVisitor(elementLookupTable, graphixRewritingContext.getMetadataProvider(),
+                        parserFactory, graphixRewritingContext.getWarningCollector());
+        topStatement.getBody().accept(elementLookupTableVisitor, null);
+        for (GraphElementDecl graphElementDecl : elementLookupTable) {
+            Query wrappedQuery = ExpressionUtils.createWrappedQuery(
+                    new GraphElementBodyExpr(graphElementDecl.getIdentifier()), graphElementDecl.getSourceLocation());
+            loadNormalizedGraphElement(graphixRewritingContext, wrappedQuery, graphElementDecl);
+        }
+
+        // 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();
+
+        // 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);
+    }
+
+    @Override
+    protected SqlppFunctionBodyRewriter getFunctionAndViewBodyRewriter() {
+        return new SqlppFunctionBodyRewriter(parserFactory) {
+            @Override
+            public void rewrite(LangRewritingContext langRewritingContext, IReturningStatement topStatement,
+                    boolean allowNonStoredUdfCalls, boolean inlineUdfsAndViews, Collection<VarIdentifier> externalVars)
+                    throws CompilationException {
+                // Perform an initial error-checking pass to validate our body.
+                LOGGER.trace("Performing pre-rewrite check (user query validation).");
+                PreRewriteCheckVisitor preRewriteCheckVisitor = new PreRewriteCheckVisitor(langRewritingContext);
+                topStatement.getBody().accept(preRewriteCheckVisitor, null);
+
+                // Perform the Graphix rewrites.
+                rewriteGraphixASTNodes(langRewritingContext, topStatement);
+
+                // 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).");
+                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.");
+            }
+        };
+    }
+
+    private Expression rewriteGraphElementBody(GraphixRewritingContext graphixRewritingContext,
+            DataverseName elementDataverse, Expression bodyExpr, List<VarIdentifier> externalVars,
+            SourceLocation sourceLoc) throws CompilationException {
+        Dataverse defaultDataverse = graphixRewritingContext.getMetadataProvider().getDefaultDataverse();
         Dataverse targetDataverse;
 
         // We might need to change our dataverse, if the element definition requires a different one.
@@ -160,18 +257,19 @@
 
         } else {
             try {
-                targetDataverse = rewriteContext.getMetadataProvider().findDataverse(elementDataverse);
+                targetDataverse = graphixRewritingContext.getMetadataProvider().findDataverse(elementDataverse);
 
             } catch (AlgebricksException e) {
                 throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, e, sourceLoc, elementDataverse);
             }
         }
-        rewriteContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
+        graphixRewritingContext.getMetadataProvider().setDefaultDataverse(targetDataverse);
 
         // Get the body of the rewritten query.
         try {
             Query wrappedQuery = ExpressionUtils.createWrappedQuery(bodyExpr, sourceLoc);
-            bodyRewriter.rewrite(rewriteContext.getLangRewritingContext(), wrappedQuery, false, false, externalVars);
+            LangRewritingContext langRewritingContext = graphixRewritingContext.getLangRewritingContext();
+            bodyRewriter.rewrite(langRewritingContext, wrappedQuery, false, false, externalVars);
             return wrappedQuery.getBody();
 
         } catch (CompilationException e) {
@@ -180,7 +278,7 @@
 
         } finally {
             // Switch back to the working dataverse.
-            rewriteContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
+            graphixRewritingContext.getMetadataProvider().setDefaultDataverse(defaultDataverse);
         }
     }
 }
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 e62d337..70e749b 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
@@ -1,58 +1,60 @@
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
+ * or more contributor license agreements. See the NOTICE file
  * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
+ * 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
- *
+ * 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
+ * KIND, either express or implied. See the License for the
  * specific language governing permissions and limitations
  * under the License.
  */
 package org.apache.asterix.graphix.lang.rewrites;
 
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.common.functions.FunctionSignature;
 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).
+ */
 public class GraphixRewritingContext {
-    private final Map<GraphElementIdentifier, GraphElementDecl> graphElementDeclMap = new HashMap<>();
     private final LangRewritingContext langRewritingContext;
 
-    public GraphixRewritingContext(MetadataProvider metadataProvider, List<FunctionDecl> declaredFunctions,
-            List<ViewDecl> declaredViews, List<GraphElementDecl> declaredGraphElements,
-            IWarningCollector warningCollector, int varCounter) {
-        if (declaredGraphElements != null) {
-            declaredGraphElements.forEach(e -> graphElementDeclMap.put(e.getIdentifier(), e));
-        }
-        langRewritingContext = new LangRewritingContext(metadataProvider, declaredFunctions, declaredViews,
-                warningCollector, varCounter);
+    public GraphixRewritingContext(LangRewritingContext langRewritingContext) {
+        this.langRewritingContext = langRewritingContext;
     }
 
-    public Map<GraphElementIdentifier, GraphElementDecl> getDeclaredGraphElements() {
-        return graphElementDeclMap;
+    public MetadataProvider getMetadataProvider() {
+        return langRewritingContext.getMetadataProvider();
+    }
+
+    public IWarningCollector getWarningCollector() {
+        return langRewritingContext.getWarningCollector();
     }
 
     public LangRewritingContext getLangRewritingContext() {
         return langRewritingContext;
     }
 
-    public MetadataProvider getMetadataProvider() {
-        return langRewritingContext.getMetadataProvider();
+    public Map<FunctionSignature, FunctionDecl> getDeclaredFunctions() {
+        return langRewritingContext.getDeclaredFunctions();
+    }
+
+    public VarIdentifier getNewVariable() {
+        VarIdentifier langRewriteGeneratedVar = langRewritingContext.newVariable();
+        String graphixGeneratedName = "#GG_" + langRewriteGeneratedVar.getValue().substring(1);
+        return new VarIdentifier(graphixGeneratedName, langRewriteGeneratedVar.getId());
     }
 }
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
new file mode 100644
index 0000000..e78d935
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/ExprAssembler.java
@@ -0,0 +1,52 @@
+/*
+ * 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/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
similarity index 74%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
index 9667fd6..25efe98 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/assembly/IExprAssembly.java
@@ -16,7 +16,14 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.rewrites.assembly;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+import org.apache.asterix.common.exceptions.CompilationException;
+
+/**
+ * @see ExprAssembler
+ */
+@FunctionalInterface
+public interface IExprAssembly<T> {
+    T apply(T input) 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
new file mode 100644
index 0000000..847c0d9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/EdgeDependencyGraph.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.common;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+/**
+ * A two-level ordered list of {@link EdgePatternExpr} instances, whose order depends on the input edge-dependency
+ * graphs (represented as an adjacency list). Ideally, we want to return the smallest set of Hamilton
+ * {@link EdgePatternExpr} paths, but this is an NP-hard problem. Instead, we find the **first** set of paths that
+ * visit each {@link EdgePatternExpr} once. This inner-order is dependent on the order of the adjacency map iterators.
+ *
+ * @see org.apache.asterix.graphix.lang.rewrites.visitor.ElementAnalysisVisitor
+ */
+public class EdgeDependencyGraph implements Iterable<Iterable<EdgePatternExpr>> {
+    private final List<Map<Identifier, List<Identifier>>> adjacencyMaps;
+    private final Map<Identifier, EdgePatternExpr> edgePatternMap;
+
+    public EdgeDependencyGraph(List<Map<Identifier, List<Identifier>>> adjacencyMaps,
+            Map<Identifier, EdgePatternExpr> edgePatternMap) {
+        this.adjacencyMaps = adjacencyMaps;
+        this.edgePatternMap = edgePatternMap;
+    }
+
+    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();
+    }
+}
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
new file mode 100644
index 0000000..747cdae
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/common/ElementLookupTable.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.common;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * Lookup table for {@link GraphElementDecl} instances, vertex keys, edge destination keys / labels, and edge source
+ * keys / labels-- indexed by {@link T} 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<>();
+
+    // 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 putVertexKey(T 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.
+        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) {
+        return graphElementDeclMap.get(identifier);
+    }
+
+    public List<List<String>> getVertexKey(T identifier) {
+        return vertexKeyMap.get(identifier);
+    }
+
+    public List<List<String>> getEdgeDestKeys(T identifier) {
+        return edgeDestKeysMap.get(identifier);
+    }
+
+    public List<List<String>> getEdgeSourceKeys(T 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() {
+        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
new file mode 100644
index 0000000..214e0a1
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedFixedPathExpansion.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.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.IGraphExpr;
+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.EdgeType#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.getEdgeClass() != IGraphExpr.GraphExprKind.PATH_PATTERN
+                || edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.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().getEdgeType(),
+                    IGraphExpr.GraphExprKind.EDGE_PATTERN, 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
new file mode 100644
index 0000000..91d58d0
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/DirectedVarPathExpansion.java
@@ -0,0 +1,72 @@
+/*
+ * 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.IGraphExpr;
+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.getEdgeClass() != IGraphExpr.GraphExprKind.PATH_PATTERN
+                || edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.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.getEdgeType(), IGraphExpr.GraphExprKind.PATH_PATTERN,
+                            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
new file mode 100644
index 0000000..cd62074
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/IEdgePatternExpansion.java
@@ -0,0 +1,32 @@
+/*
+ * 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
new file mode 100644
index 0000000..7cc63dd
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/PathEnumerationEnvironment.java
@@ -0,0 +1,136 @@
+/*
+ * 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
new file mode 100644
index 0000000..f01ba0d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedEdgeExpansion.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.expand;
+
+import java.util.List;
+
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.IGraphExpr;
+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.EdgeType#LEFT_TO_RIGHT},and another edge directed from
+ * {@link EdgeDescriptor.EdgeType#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.getEdgeClass() != IGraphExpr.GraphExprKind.EDGE_PATTERN
+                || edgeDescriptor.getEdgeType() != EdgeDescriptor.EdgeType.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.EdgeType.LEFT_TO_RIGHT, IGraphExpr.GraphExprKind.EDGE_PATTERN,
+                        edgeDescriptor.getEdgeLabels(), edgeDescriptor.getVariableExpr(), null, null);
+        EdgePatternExpr leftToRightEdge = new EdgePatternExpr(leftVertex, rightVertex, leftToRightEdgeDescriptor);
+
+        // ...and our RIGHT_TO_LEFT edge...
+        EdgeDescriptor rightToLeftEdgeDescriptor =
+                new EdgeDescriptor(EdgeDescriptor.EdgeType.RIGHT_TO_LEFT, IGraphExpr.GraphExprKind.EDGE_PATTERN,
+                        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
new file mode 100644
index 0000000..044a58b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedFixedPathExpansion.java
@@ -0,0 +1,110 @@
+/*
+ * 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.IGraphExpr;
+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.EdgeType#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.getEdgeClass() != IGraphExpr.GraphExprKind.PATH_PATTERN
+                || edgeDescriptor.getEdgeType() != EdgeDescriptor.EdgeType.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.EdgeType.LEFT_TO_RIGHT, IGraphExpr.GraphExprKind.EDGE_PATTERN,
+                                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.EdgeType.RIGHT_TO_LEFT, IGraphExpr.GraphExprKind.EDGE_PATTERN,
+                                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
new file mode 100644
index 0000000..9c85b77
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/expand/UndirectedVarPathExpansion.java
@@ -0,0 +1,72 @@
+/*
+ * 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.IGraphExpr;
+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.getEdgeClass() != IGraphExpr.GraphExprKind.PATH_PATTERN
+                || edgeDescriptor.getEdgeType() != EdgeDescriptor.EdgeType.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.EdgeType.UNDIRECTED, IGraphExpr.GraphExprKind.PATH_PATTERN,
+                            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/GraphixLowerSupplier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/GraphixLowerSupplier.java
new file mode 100644
index 0000000..8433a9f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/GraphixLowerSupplier.java
@@ -0,0 +1,168 @@
+/*
+ * 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
new file mode 100644
index 0000000..1c24588
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierContext.java
@@ -0,0 +1,78 @@
+/*
+ * 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
new file mode 100644
index 0000000..0d07ae2
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/LowerSupplierNode.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.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/assembly/AbstractLowerAssembly.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/AbstractLowerAssembly.java
new file mode 100644
index 0000000..e7459e0
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/AbstractLowerAssembly.java
@@ -0,0 +1,105 @@
+/*
+ * 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
new file mode 100644
index 0000000..7c93d2b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/DanglingVertexLowerAssembly.java
@@ -0,0 +1,82 @@
+/*
+ * 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
new file mode 100644
index 0000000..88575ec
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/ExpandEdgeLowerAssembly.java
@@ -0,0 +1,315 @@
+/*
+ * 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.IGraphExpr;
+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.getEdgeType() != EdgeDescriptor.EdgeType.UNDIRECTED
+                && edgeDescriptor.getEdgeClass() == IGraphExpr.GraphExprKind.EDGE_PATTERN) {
+            // We have a single directed edge. We do not need to break this up.
+            return List.of(List.of(edgePatternExpr));
+
+        } else if (edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.UNDIRECTED
+                && edgeDescriptor.getEdgeClass() == IGraphExpr.GraphExprKind.EDGE_PATTERN) {
+            return new UndirectedEdgeExpansion().expand(edgePatternExpr);
+
+        } else { // edgeDescriptor.getEdgeClass() == IGraphExpr.GraphExprKind.PATH_PATTERN
+            PathEnumerationEnvironment decompositionEnvironment = new PathEnumerationEnvironment(edgePatternExpr,
+                    lowerSupplierContext, generatedVertexConjuncts::addLast, generatedEdgeConjuncts::addLast,
+                    generatedReboundExpressions::addLast);
+
+            IEdgePatternExpansion edgePatternDecomposition;
+            if (edgeDescriptor.getEdgeType() != EdgeDescriptor.EdgeType.UNDIRECTED
+                    && Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
+                edgePatternDecomposition = new DirectedFixedPathExpansion(decompositionEnvironment);
+
+            } else if (edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.UNDIRECTED
+                    && Objects.equals(edgeDescriptor.getMinimumHops(), edgeDescriptor.getMaximumHops())) {
+                edgePatternDecomposition = new UndirectedFixedPathExpansion(decompositionEnvironment);
+
+            } else if (edgeDescriptor.getEdgeType() != EdgeDescriptor.EdgeType.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.getEdgeType() == EdgeDescriptor.EdgeType.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
new file mode 100644
index 0000000..bba86fd
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/IsomorphismLowerAssembly.java
@@ -0,0 +1,258 @@
+/*
+ * 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.IGraphExpr;
+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.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().getEdgeClass() == IGraphExpr.GraphExprKind.EDGE_PATTERN) {
+                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
new file mode 100644
index 0000000..54b00e9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/lower/assembly/NamedPathLowerAssembly.java
@@ -0,0 +1,131 @@
+/*
+ * 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.IGraphExpr;
+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.getEdgeClass() == IGraphExpr.GraphExprKind.EDGE_PATTERN) {
+                        // "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() == IGraphExpr.GraphExprKind.PATH_PATTERN
+                        // 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/print/GraphixASTPrintVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitor.java
new file mode 100644
index 0000000..6c6482e
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitor.java
@@ -0,0 +1,269 @@
+/*
+ * 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.print;
+
+import java.io.PrintWriter;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.visitor.IGraphixLangVisitor;
+import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
+import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.visitor.SqlppAstPrintVisitor;
+
+public class GraphixASTPrintVisitor extends SqlppAstPrintVisitor implements IGraphixLangVisitor<Void, Integer> {
+    public GraphixASTPrintVisitor(PrintWriter out) {
+        super(out);
+    }
+
+    @Override
+    public Void visit(GraphConstructor graphConstructor, Integer step) throws CompilationException {
+        out.println(skip(step) + "GRAPH [");
+        for (GraphConstructor.VertexConstructor vertexConstructor : graphConstructor.getVertexElements()) {
+            vertexConstructor.accept(this, step + 1);
+        }
+        for (GraphConstructor.EdgeConstructor edgeConstructor : graphConstructor.getEdgeElements()) {
+            edgeConstructor.accept(this, step + 1);
+        }
+        out.println(skip(step) + "]");
+        return null;
+    }
+
+    @Override
+    public Void visit(GraphConstructor.VertexConstructor vertexConstructor, Integer step) throws CompilationException {
+        out.print(skip(step) + "VERTEX ");
+        out.println("(:" + vertexConstructor.getLabel() + ")");
+        out.println(skip(step) + "AS ");
+        vertexConstructor.getExpression().accept(this, step + 1);
+        return null;
+    }
+
+    @Override
+    public Void visit(GraphConstructor.EdgeConstructor edgeConstructor, Integer step) throws CompilationException {
+        out.print(skip(step) + "EDGE ");
+        out.print("(:" + edgeConstructor.getSourceLabel() + ")");
+        out.print("-[:" + edgeConstructor.getEdgeLabel() + "]->");
+        out.println("(:" + edgeConstructor.getDestinationLabel() + ")");
+        out.println(skip(step) + "AS ");
+        edgeConstructor.getExpression().accept(this, step + 1);
+        return null;
+    }
+
+    @Override
+    public Void visit(SelectBlock selectBlock, Integer step) throws CompilationException {
+        return (selectBlock instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) selectBlock, step)
+                : super.visit(selectBlock, step);
+    }
+
+    @Override
+    public Void visit(GraphSelectBlock graphSelectBlock, Integer step) throws CompilationException {
+        graphSelectBlock.getSelectClause().accept(this, step);
+        if (graphSelectBlock.hasFromClause()) {
+            graphSelectBlock.getFromClause().accept(this, step);
+
+        } else if (graphSelectBlock.hasFromGraphClause()) {
+            graphSelectBlock.getFromGraphClause().accept(this, step);
+        }
+        if (graphSelectBlock.hasLetWhereClauses()) {
+            for (AbstractClause letWhereClause : graphSelectBlock.getLetWhereList()) {
+                letWhereClause.accept(this, step);
+            }
+        }
+        if (graphSelectBlock.hasGroupbyClause()) {
+            graphSelectBlock.getGroupbyClause().accept(this, step);
+            if (graphSelectBlock.hasLetHavingClausesAfterGroupby()) {
+                for (AbstractClause letHavingClause : graphSelectBlock.getLetHavingListAfterGroupby()) {
+                    letHavingClause.accept(this, step);
+                }
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(FromGraphClause fromGraphClause, Integer step) throws CompilationException {
+        out.println(skip(step) + "FROM [");
+        if (fromGraphClause.getGraphConstructor() != null) {
+            fromGraphClause.getGraphConstructor().accept(this, step + 1);
+
+        } else {
+            out.print(skip(step + 1) + "GRAPH ");
+            if (fromGraphClause.getDataverseName() != null) {
+                out.print(fromGraphClause.getDataverseName().toString());
+                out.print(".");
+            }
+            out.print(fromGraphClause.getGraphName());
+        }
+        out.println(skip(step) + "]");
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            matchClause.accept(this, step);
+        }
+        for (AbstractBinaryCorrelateClause correlateClause : fromGraphClause.getCorrelateClauses()) {
+            correlateClause.accept(this, step);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(MatchClause matchClause, Integer step) throws CompilationException {
+        out.print(skip(step));
+        switch (matchClause.getMatchType()) {
+            case LEADING:
+            case INNER:
+                out.println("MATCH [");
+                break;
+            case LEFTOUTER:
+                out.println("LEFT MATCH [");
+                break;
+        }
+        for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+            pathExpression.accept(this, step + 1);
+        }
+        out.println(skip(step) + "]");
+        return null;
+    }
+
+    @Override
+    public Void visit(PathPatternExpr pathPatternExpr, Integer step) throws CompilationException {
+        List<VertexPatternExpr> danglingVertices = pathPatternExpr.getVertexExpressions().stream().filter(v -> {
+            // Collect all vertices that are not attached to an edge.
+            for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
+                VertexPatternExpr leftVertex = edgeExpression.getLeftVertex();
+                VertexPatternExpr rightVertex = edgeExpression.getRightVertex();
+                if (leftVertex == v || rightVertex == v) {
+                    return false;
+                }
+            }
+            return true;
+        }).collect(Collectors.toList());
+        int index = 0;
+        for (VertexPatternExpr vertexPatternExpr : danglingVertices) {
+            if (index > 0) {
+                out.print(skip(step) + ",");
+            }
+            vertexPatternExpr.accept(this, step);
+            out.println();
+            index++;
+        }
+        for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
+            if (index > 0) {
+                out.print(skip(step) + ",");
+            }
+            edgeExpression.accept(this, step);
+            out.println();
+            index++;
+        }
+        if (pathPatternExpr.getVariableExpr() != null) {
+            out.print(skip(step) + "AS ");
+            pathPatternExpr.getVariableExpr().accept(this, 0);
+        }
+        return null;
+    }
+
+    @Override
+    public Void visit(EdgePatternExpr edgePatternExpr, Integer step) throws CompilationException {
+        out.print(skip(step));
+        edgePatternExpr.getLeftVertex().accept(this, 0);
+        switch (edgePatternExpr.getEdgeDescriptor().getEdgeType()) {
+            case LEFT_TO_RIGHT:
+            case UNDIRECTED:
+                out.print("-[");
+                break;
+            case RIGHT_TO_LEFT:
+                out.print("<-[");
+                break;
+        }
+        if (edgePatternExpr.getEdgeDescriptor().getVariableExpr() != null) {
+            out.print(edgePatternExpr.getEdgeDescriptor().getVariableExpr().getVar().getValue());
+        }
+        out.print(":(");
+        int index = 0;
+        for (ElementLabel label : edgePatternExpr.getEdgeDescriptor().getEdgeLabels()) {
+            if (index > 0) {
+                out.print("|");
+            }
+            out.print(label);
+            index++;
+        }
+        out.print("){");
+        out.print(edgePatternExpr.getEdgeDescriptor().getMinimumHops().toString());
+        out.print(",");
+        out.print(edgePatternExpr.getEdgeDescriptor().getMaximumHops().toString());
+        out.print("}");
+        switch (edgePatternExpr.getEdgeDescriptor().getEdgeType()) {
+            case LEFT_TO_RIGHT:
+                out.print("]->");
+                break;
+            case RIGHT_TO_LEFT:
+            case UNDIRECTED:
+                out.print("]-");
+                break;
+        }
+        edgePatternExpr.getRightVertex().accept(this, 0);
+        return null;
+    }
+
+    @Override
+    public Void visit(VertexPatternExpr vertexPatternExpr, Integer step) throws CompilationException {
+        out.print(skip(step) + "(");
+        if (vertexPatternExpr.getVariableExpr() != null) {
+            out.print(vertexPatternExpr.getVariableExpr().getVar().getValue());
+        }
+        out.print(":");
+        int index = 0;
+        for (ElementLabel label : vertexPatternExpr.getLabels()) {
+            if (index > 0) {
+                out.print("|");
+            }
+            out.print(label);
+            index++;
+        }
+        out.print(")");
+        return null;
+    }
+
+    // The following should not appear in queries (the former, pre-rewrite).
+    @Override
+    public Void visit(CreateGraphStatement createGraphStatement, Integer step) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Void visit(GraphElementDecl graphElementDecl, Integer step) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Void visit(GraphDropStatement graphDropStatement, Integer step) throws CompilationException {
+        return null;
+    }
+}
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
similarity index 64%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
index 9667fd6..ff00077 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/GraphixASTPrintVisitorFactory.java
@@ -16,7 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.rewrites.print;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+import java.io.PrintWriter;
+
+import org.apache.asterix.lang.common.base.IAstPrintVisitorFactory;
+import org.apache.asterix.lang.common.visitor.QueryPrintVisitor;
+
+public class GraphixASTPrintVisitorFactory implements IAstPrintVisitorFactory {
+    @Override
+    public QueryPrintVisitor createLangVisitor(PrintWriter writer) {
+        return new GraphixASTPrintVisitor(writer);
+    }
+}
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
new file mode 100644
index 0000000..4493937
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/print/SqlppASTPrintQueryVisitor.java
@@ -0,0 +1,681 @@
+/*
+ * 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.print;
+
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+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.GroupbyClause;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.LimitClause;
+import org.apache.asterix.lang.common.clause.OrderbyClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldAccessor;
+import org.apache.asterix.lang.common.expression.GbyVariableExpressionPair;
+import org.apache.asterix.lang.common.expression.IfExpr;
+import org.apache.asterix.lang.common.expression.IndexAccessor;
+import org.apache.asterix.lang.common.expression.ListConstructor;
+import org.apache.asterix.lang.common.expression.ListSliceExpression;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.apache.asterix.lang.common.expression.OperatorExpr;
+import org.apache.asterix.lang.common.expression.QuantifiedExpression;
+import org.apache.asterix.lang.common.expression.RecordConstructor;
+import org.apache.asterix.lang.common.expression.UnaryExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+import org.apache.asterix.lang.common.statement.Query;
+import org.apache.asterix.lang.common.struct.OperatorType;
+import org.apache.asterix.lang.sqlpp.clause.FromClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.HavingClause;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.NestClause;
+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.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.expression.CaseExpression;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
+import org.apache.asterix.lang.sqlpp.optype.SetOpType;
+import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppQueryExpressionVisitor;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.hyracks.algebricks.common.exceptions.NotImplementedException;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+
+/**
+ * Visitor class to create a valid SQL++ query out of a **valid** AST. 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).
+ */
+public class SqlppASTPrintQueryVisitor extends AbstractSqlppQueryExpressionVisitor<String, Void> {
+    private final PrintWriter printWriter;
+
+    public SqlppASTPrintQueryVisitor(PrintWriter printWriter) {
+        this.printWriter = printWriter;
+    }
+
+    @Override
+    public String visit(Query query, Void arg) throws CompilationException {
+        String queryBodyString = query.getBody().accept(this, arg) + ";";
+        queryBodyString = queryBodyString.trim().replaceAll("\\s+", " ");
+        printWriter.print(queryBodyString);
+        return null;
+    }
+
+    @Override
+    public String visit(SelectExpression selectStatement, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" ( ");
+        if (selectStatement.hasLetClauses()) {
+            sb.append(" LET ");
+            sb.append(selectStatement.getLetList().stream().map(this::visitAndSwallowException)
+                    .collect(Collectors.joining(", ")));
+        }
+        sb.append(selectStatement.getSelectSetOperation().accept(this, arg));
+        if (selectStatement.hasOrderby()) {
+            sb.append(selectStatement.getOrderbyClause().accept(this, arg));
+        }
+        if (selectStatement.hasLimit()) {
+            sb.append(selectStatement.getLimitClause().accept(this, arg));
+        }
+        sb.append(" ) ");
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(LetClause letClause, Void arg) throws CompilationException {
+        return String.format(" %s =  ( %s ) ", letClause.getVarExpr().accept(this, arg),
+                letClause.getBindingExpr().accept(this, arg));
+    }
+
+    @Override
+    public String visit(SelectSetOperation selectSetOperation, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(selectSetOperation.getLeftInput().accept(this, arg));
+        if (selectSetOperation.hasRightInputs()) {
+            sb.append(selectSetOperation.getRightInputs().stream().map(s -> {
+                // Ignore everything else but UNION-ALL.
+                if (s.getSetOpType() == SetOpType.UNION) {
+                    SetOperationInput rightInput = s.getSetOperationRightInput();
+                    if (!s.isSetSemantics()) {
+                        if (rightInput.selectBlock()) {
+                            return " UNION ALL " + visitAndSwallowException(rightInput.getSelectBlock());
+                        }
+                        if (rightInput.subquery()) {
+                            return " UNION ALL " + visitAndSwallowException(rightInput.getSubquery());
+                        }
+                    }
+                }
+                throw new NotImplementedException("Given SET operation is not implemented.");
+            }).collect(Collectors.joining()));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(SelectBlock selectBlock, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(visitAndSwallowException(selectBlock.getSelectClause()));
+        if (selectBlock.hasFromClause()) {
+            sb.append(selectBlock.getFromClause().accept(this, arg));
+        }
+        if (selectBlock.hasLetWhereClauses()) {
+            List<LetClause> letClauses = selectBlock.getLetWhereList().stream()
+                    .filter(c -> c.getClauseType() == Clause.ClauseType.LET_CLAUSE).map(c -> (LetClause) c)
+                    .collect(Collectors.toList());
+            List<WhereClause> whereClauses = selectBlock.getLetWhereList().stream()
+                    .filter(c -> c.getClauseType() == Clause.ClauseType.WHERE_CLAUSE).map(c -> (WhereClause) c)
+                    .collect(Collectors.toList());
+            if (!letClauses.isEmpty()) {
+                sb.append(" LET ");
+                sb.append(letClauses.stream().map(this::visitAndSwallowException).collect(Collectors.joining(", ")));
+            }
+            if (!whereClauses.isEmpty()) {
+                sb.append(" WHERE ");
+                sb.append(
+                        whereClauses.stream().map(this::visitAndSwallowException).collect(Collectors.joining(" AND ")));
+            }
+        }
+        if (selectBlock.hasGroupbyClause()) {
+            sb.append(selectBlock.getGroupbyClause().accept(this, arg));
+        }
+        if (selectBlock.hasLetHavingClausesAfterGroupby()) {
+            List<LetClause> letClauses = selectBlock.getLetHavingListAfterGroupby().stream()
+                    .filter(c -> c.getClauseType() == Clause.ClauseType.LET_CLAUSE).map(c -> (LetClause) c)
+                    .collect(Collectors.toList());
+            List<HavingClause> havingClauses = selectBlock.getLetHavingListAfterGroupby().stream()
+                    .filter(c -> c.getClauseType() == Clause.ClauseType.HAVING_CLAUSE).map(c -> (HavingClause) c)
+                    .collect(Collectors.toList());
+            if (!letClauses.isEmpty()) {
+                sb.append(" LET ");
+                sb.append(letClauses.stream().map(this::visitAndSwallowException).collect(Collectors.joining(", ")));
+            }
+            if (!havingClauses.isEmpty()) {
+                sb.append(" HAVING ");
+                sb.append(havingClauses.stream().map(this::visitAndSwallowException).collect(Collectors.joining(", ")));
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(FromClause fromClause, Void arg) {
+        return String.format(" FROM %s ", fromClause.getFromTerms().stream().map(this::visitAndSwallowException)
+                .collect(Collectors.joining(", ")));
+    }
+
+    @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()));
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(WhereClause whereClause, Void arg) throws CompilationException {
+        return whereClause.getWhereExpr().accept(this, arg);
+    }
+
+    @Override
+    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(", ")));
+        if (groupbyClause.hasGroupVar()) {
+            sb.append(" GROUP AS ");
+            sb.append(groupbyClause.getGroupVar().accept(this, arg));
+            if (groupbyClause.hasGroupFieldList()) {
+                sb.append(" ( ");
+                sb.append(
+                        groupbyClause.getGroupFieldList().stream()
+                                .map(f -> visitAndSwallowException(f.first) + " AS "
+                                        + formatIdentifierForQuery(f.second.getValue()))
+                                .collect(Collectors.joining(", ")));
+                sb.append(" ) ");
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(HavingClause havingClause, Void arg) throws CompilationException {
+        return havingClause.getFilterExpression().accept(this, arg);
+    }
+
+    @Override
+    public String visit(SelectClause selectClause, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" SELECT ");
+        if (selectClause.distinct()) {
+            sb.append(" DISTINCT ");
+        }
+        if (selectClause.selectElement()) {
+            sb.append(" VALUE ");
+            sb.append(selectClause.getSelectElement().accept(this, arg));
+
+        } else {
+            sb.append(selectClause.getSelectRegular().accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(SelectElement selectElement, Void arg) throws CompilationException {
+        return selectElement.getExpression().accept(this, arg);
+    }
+
+    @Override
+    public String visit(SelectRegular selectRegular, Void arg) {
+        return selectRegular.getProjections().stream().map(this::visitAndSwallowException)
+                .collect(Collectors.joining(", "));
+    }
+
+    @Override
+    public String visit(Projection projection, Void arg) throws CompilationException {
+        switch (projection.getKind()) {
+            case NAMED_EXPR:
+                return projection.getExpression().accept(this, arg) + " AS `"
+                        + formatIdentifierForQuery(projection.getName()) + "`";
+            case STAR:
+                return "*";
+            case VAR_STAR:
+                return projection.getExpression().accept(this, arg) + ".*";
+            default:
+                throw new NotImplementedException("Illegal projection type encountered!");
+        }
+    }
+
+    @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:
+                sb.append(" INNER JOIN ");
+                break;
+            case LEFTOUTER:
+                sb.append(" LEFT OUTER JOIN ");
+                break;
+            case RIGHTOUTER:
+                sb.append(" RIGHT OUTER JOIN ");
+                break;
+        }
+        sb.append(joinClause.getRightExpression().accept(this, arg));
+        if (joinClause.getRightVariable() != null) {
+            sb.append(" AS ");
+            sb.append(joinClause.getRightVariable().accept(this, arg));
+        }
+        sb.append(" ON ");
+        sb.append(joinClause.getConditionExpression().accept(this, arg));
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(NestClause nestClause, Void arg) {
+        throw new NotImplementedException("NEST clause not implemented!");
+    }
+
+    @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:
+                sb.append(" UNNEST ");
+                break;
+            case LEFTOUTER:
+                sb.append(" LEFT OUTER UNNEST ");
+                break;
+        }
+        sb.append(unnestClause.getRightExpression().accept(this, arg));
+        if (unnestClause.getRightVariable() != null) {
+            sb.append(" AS ");
+            sb.append(unnestClause.getRightVariable().accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(OrderbyClause orderbyClause, Void arg) {
+        String orderByString = IntStream.range(0, orderbyClause.getOrderbyList().size()).mapToObj(i -> {
+            // Build the order-modifier as a string.
+            OrderbyClause.OrderModifier orderModifier = orderbyClause.getModifierList().get(i);
+            String orderModifierAsString = (orderModifier == null) ? "" : orderModifier.toString();
+
+            // Build our null-order-modifier as a string.
+            OrderbyClause.NullOrderModifier nullOrderModifier = orderbyClause.getNullModifierList().get(i);
+            String nullOrderModifierAsString = "";
+            if (nullOrderModifier == OrderbyClause.NullOrderModifier.FIRST) {
+                nullOrderModifierAsString = "NULLS FIRST";
+            } else if (nullOrderModifier == OrderbyClause.NullOrderModifier.LAST) {
+                nullOrderModifierAsString = "NULLS LAST";
+            }
+
+            // Finally, return the string expression.
+            Expression orderByExpr = orderbyClause.getOrderbyList().get(i);
+            return visitAndSwallowException(orderByExpr) + " " + orderModifierAsString + " " + nullOrderModifierAsString
+                    + " ";
+        }).collect(Collectors.joining(", "));
+        return String.format(" ORDER BY %s ", orderByString);
+    }
+
+    @Override
+    public String visit(LimitClause limitClause, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        if (limitClause.hasLimitExpr()) {
+            sb.append(" LIMIT ");
+            sb.append(limitClause.getLimitExpr().accept(this, arg));
+        }
+        if (limitClause.hasOffset()) {
+            sb.append(" OFFSET ");
+            sb.append(limitClause.getOffset().accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(CallExpr callExpr, Void arg) throws CompilationException {
+        FunctionIdentifier functionIdentifier = callExpr.getFunctionSignature().createFunctionIdentifier();
+        if (functionIdentifier.equals(BuiltinFunctions.DATASET)) {
+            // We need to undo the DATASET call here.
+            LiteralExpr dataverseNameExpr = (LiteralExpr) callExpr.getExprList().get(0);
+            LiteralExpr datasetNameExpr = (LiteralExpr) callExpr.getExprList().get(1);
+            String dataverseName = dataverseNameExpr.getValue().getStringValue();
+            String datasetName = datasetNameExpr.getValue().getStringValue();
+            return String.format("`%s`.`%s`", dataverseName, datasetName);
+
+        } else if (functionIdentifier.equals(BuiltinFunctions.IS_MISSING)) {
+            return String.format(" %s IS MISSING ", callExpr.getExprList().get(0).accept(this, arg));
+
+        } else if (functionIdentifier.equals(BuiltinFunctions.IS_NULL)) {
+            return String.format(" %s IS NULL ", callExpr.getExprList().get(0).accept(this, arg));
+
+        } else if (functionIdentifier.equals(BuiltinFunctions.IS_UNKNOWN)) {
+            return String.format(" %s IS UNKNOWN ", callExpr.getExprList().get(0).accept(this, arg));
+
+        } else {
+            String functionName = functionIdentifier.getName().toUpperCase().replaceAll("-", "_");
+            return String.format(" %s ( %s ) ", functionName, callExpr.getExprList().stream()
+                    .map(this::visitAndSwallowException).collect(Collectors.joining(", ")));
+        }
+    }
+
+    @Override
+    public String visit(LiteralExpr literalExpr, Void arg) {
+        switch (literalExpr.getValue().getLiteralType()) {
+            case STRING:
+                return "\"" + literalExpr.getValue().getStringValue() + "\"";
+
+            case INTEGER:
+            case FLOAT:
+            case DOUBLE:
+            case LONG:
+                return literalExpr.getValue().getStringValue();
+
+            case MISSING:
+            case NULL:
+            case TRUE:
+            case FALSE:
+                return literalExpr.getValue().getStringValue().toUpperCase();
+        }
+        return null;
+    }
+
+    @Override
+    public String visit(VariableExpr variableExpr, Void arg) {
+        return "`" + formatIdentifierForQuery(variableExpr.getVar().getValue()) + "`";
+    }
+
+    @Override
+    public String visit(ListConstructor listConstructor, Void arg) {
+        StringBuilder sb = new StringBuilder();
+        if (listConstructor.getType() == ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR) {
+            sb.append(" [ ");
+            sb.append(listConstructor.getExprList().stream().map(this::visitAndSwallowException)
+                    .collect(Collectors.joining(", ")));
+            sb.append(" ] ");
+
+        } else {
+            sb.append(" {{ ");
+            sb.append(listConstructor.getExprList().stream().map(this::visitAndSwallowException)
+                    .collect(Collectors.joining(", ")));
+            sb.append(" }} ");
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(RecordConstructor recordConstructor, Void arg) {
+        String recordConstructorString = recordConstructor.getFbList().stream()
+                .map(f -> formatIdentifierForQuery(visitAndSwallowException(f.getLeftExpr())) + " : "
+                        + visitAndSwallowException(f.getRightExpr()))
+                .collect(Collectors.joining(", "));
+        return String.format(" { %s } ", recordConstructorString);
+    }
+
+    @Override
+    public String visit(OperatorExpr operatorExpr, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(operatorExpr.getExprList().get(0).accept(this, arg));
+        for (int i = 0; i < operatorExpr.getOpList().size(); i++) {
+            OperatorType operatorType = operatorExpr.getOpList().get(i);
+            switch (operatorType) {
+                case OR:
+                    sb.append(" OR ");
+                    break;
+                case AND:
+                    sb.append(" AND ");
+                    break;
+                case LT:
+                    sb.append(" < ");
+                    break;
+                case GT:
+                    sb.append(" > ");
+                    break;
+                case LE:
+                    sb.append(" <= ");
+                    break;
+                case GE:
+                    sb.append(" >= ");
+                    break;
+                case EQ:
+                    sb.append(" = ");
+                    break;
+                case NEQ:
+                    sb.append(" != ");
+                    break;
+                case PLUS:
+                    sb.append(" + ");
+                    break;
+                case MINUS:
+                    sb.append(" - ");
+                    break;
+                case CONCAT:
+                    sb.append(" || ");
+                    break;
+                case MUL:
+                    sb.append(" * ");
+                    break;
+                case DIVIDE:
+                    sb.append(" / ");
+                    break;
+                case DIV:
+                    sb.append(" DIV ");
+                    break;
+                case MOD:
+                    sb.append(" MOD ");
+                    break;
+                case CARET:
+                    sb.append(" ^ ");
+                    break;
+                case LIKE:
+                    sb.append(" LIKE ");
+                    break;
+                case NOT_LIKE:
+                    sb.append(" NOT LIKE ");
+                    break;
+                case IN:
+                    sb.append(" IN ");
+                    break;
+                case NOT_IN:
+                    sb.append(" NOT IN ");
+                    break;
+                case BETWEEN:
+                    sb.append(" BETWEEN ");
+                    break;
+                case NOT_BETWEEN:
+                    sb.append(" NOT BETWEEN ");
+                    break;
+                case DISTINCT:
+                    sb.append(" DISTINCT ");
+                    break;
+                case NOT_DISTINCT:
+                    sb.append(" NOT DISTINCT ");
+                    break;
+                default:
+                    throw new IllegalStateException("Illegal operator encountered!");
+            }
+            sb.append(operatorExpr.getExprList().get(i + 1).accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(FieldAccessor fieldAccessor, Void arg) throws CompilationException {
+        return fieldAccessor.getExpr().accept(this, arg) + ".`"
+                + formatIdentifierForQuery(fieldAccessor.getIdent().getValue()) + "`";
+    }
+
+    @Override
+    public String visit(IndexAccessor indexAccessor, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(indexAccessor.getExpr().accept(this, arg));
+        sb.append(" [ ");
+        switch (indexAccessor.getIndexKind()) {
+            case ELEMENT:
+                sb.append(indexAccessor.getIndexExpr().accept(this, arg));
+                break;
+            case STAR:
+                sb.append(" * ");
+                break;
+            default:
+                throw new NotImplementedException("Cannot handle index accessor of ANY kind.");
+        }
+        sb.append(" ] ");
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(ListSliceExpression listSliceExpression, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(listSliceExpression.getStartIndexExpression().accept(this, arg));
+        sb.append(" : ");
+        if (listSliceExpression.hasEndExpression()) {
+            sb.append(listSliceExpression.getEndIndexExpression().accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(UnaryExpr unaryExpr, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        if (unaryExpr.getExprType() != null) {
+            switch (unaryExpr.getExprType()) {
+                case POSITIVE:
+                    sb.append("+");
+                case NEGATIVE:
+                    sb.append("-");
+                    break;
+                case EXISTS:
+                    sb.append("EXISTS");
+                    break;
+                case NOT_EXISTS:
+                    sb.append("NOT EXISTS");
+                    break;
+            }
+        }
+        sb.append(unaryExpr.getExpr().accept(this, arg));
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(IfExpr ifExpr, Void arg) {
+        throw new NotImplementedException("IfExpr are not available in SQL++!");
+    }
+
+    @Override
+    public String visit(QuantifiedExpression quantifiedExpression, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" ( ");
+        if (quantifiedExpression.getQuantifier() == QuantifiedExpression.Quantifier.SOME) {
+            sb.append(" SOME ");
+
+        } else if (quantifiedExpression.getQuantifier() == QuantifiedExpression.Quantifier.SOME_AND_EVERY) {
+            sb.append(" SOME AND EVERY ");
+
+        } else {
+            sb.append(" EVERY ");
+        }
+        sb.append(quantifiedExpression.getQuantifiedList().stream()
+                .map(q -> visitAndSwallowException(q.getVarExpr()) + " IN " + visitAndSwallowException(q.getExpr()))
+                .collect(Collectors.joining(", ")));
+        sb.append(" SATISFIES ");
+        sb.append(quantifiedExpression.getSatisfiesExpr().accept(this, arg));
+        sb.append(" END ) ");
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(CaseExpression caseExpression, Void arg) throws CompilationException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(" CASE ");
+        for (int i = 0; i < caseExpression.getWhenExprs().size(); i++) {
+            Expression whenExpr = caseExpression.getWhenExprs().get(i);
+            Expression thenExpr = caseExpression.getThenExprs().get(i);
+            sb.append(" WHEN ");
+            sb.append(whenExpr.accept(this, arg));
+            sb.append(" THEN ");
+            sb.append(thenExpr.accept(this, arg));
+        }
+        if (caseExpression.getElseExpr() != null) {
+            sb.append(" ELSE ");
+            sb.append(caseExpression.getElseExpr().accept(this, arg));
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public String visit(WindowExpression windowExpression, Void arg) {
+        // TODO: Add support for WINDOW-EXPR here.
+        throw new NotImplementedException("WindowExpression not currently supported!");
+    }
+
+    // Little helper method to dispatch within streams.
+    private String visitAndSwallowException(ILangExpression e) {
+        try {
+            return e.accept(this, null);
+
+        } catch (CompilationException ex) {
+            throw new IllegalStateException("We should never reach here!");
+        }
+    }
+
+    private String formatIdentifierForQuery(String identifier) {
+        if (identifier.contains("$")) {
+            return identifier.replace("$", "");
+
+        } else if (identifier.contains("#GG")) { // This is a Graphix generated identifier.
+            return identifier.replace("#GG_", "GGV_");
+
+        } else if (identifier.contains("#")) { // This is a SQL++ generated identifier.
+            return identifier.replace("#", "SGV_");
+
+        } else {
+            return identifier;
+        }
+    }
+}
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
new file mode 100644
index 0000000..81893e1
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/EdgeRecord.java
@@ -0,0 +1,150 @@
+/*
+ * 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.getEdgeType().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/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java
similarity index 62%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java
index 9667fd6..2a4d8c5 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/IElementRecord.java
@@ -16,7 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.lang.rewrites.record;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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
new file mode 100644
index 0000000..6f6e074
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/PathRecord.java
@@ -0,0 +1,69 @@
+/*
+ * 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
new file mode 100644
index 0000000..e8b613c
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/record/VertexRecord.java
@@ -0,0 +1,120 @@
+/*
+ * 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
new file mode 100644
index 0000000..c6e7043
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/IGraphElementResolver.java
@@ -0,0 +1,37 @@
+/*
+ * 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;
+
+/**
+ * @see org.apache.asterix.graphix.lang.rewrites.visitor.ElementResolutionVisitor
+ */
+public interface IGraphElementResolver {
+    /**
+     * @param fromGraphClause FROM-GRAPH-CLAUSE to resolve edge & vertex labels, and edge directions for.
+     */
+    void resolve(FromGraphClause fromGraphClause) throws CompilationException;
+
+    /**
+     * @return True if we cannot resolve any more elements. False otherwise.
+     */
+    boolean isAtFixedPoint();
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
new file mode 100644
index 0000000..9b513cb
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/InferenceBasedResolver.java
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.resolve;
+
+import java.util.Set;
+import java.util.function.BiFunction;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.IGraphExpr;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.visitor.LabelConsistencyVisitor;
+import org.apache.asterix.graphix.lang.rewrites.visitor.QueryKnowledgeVisitor;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * Recursively attempt to resolve any element labels / edge directions in a FROM-GRAPH-CLAUSE.
+ */
+public class InferenceBasedResolver implements IGraphElementResolver {
+    public static final String METADATA_CONFIG_NAME = "inference-based";
+
+    private final QueryKnowledgeVisitor queryKnowledgeVisitor;
+    private final SchemaKnowledgeTable schemaKnowledgeTable;
+    private boolean isAtFixedPoint = false;
+
+    public InferenceBasedResolver(SchemaKnowledgeTable schemaKnowledgeTable) {
+        this.queryKnowledgeVisitor = new QueryKnowledgeVisitor();
+        this.schemaKnowledgeTable = schemaKnowledgeTable;
+    }
+
+    @Override
+    public void resolve(FromGraphClause fromGraphClause) throws CompilationException {
+        isAtFixedPoint = true;
+
+        // Update our query knowledge, then unify vertices across our FROM-GRAPH-CLAUSE.
+        queryKnowledgeVisitor.visit(fromGraphClause, null);
+        new LabelConsistencyVisitor(queryKnowledgeVisitor.getQueryKnowledgeTable()).visit(fromGraphClause, null);
+
+        // Perform our resolution.
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            for (PathPatternExpr pathExpression : matchClause.getPathExpressions()) {
+                // We can only infer labels on edges. We ignore dangling vertices.
+                for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+                    isAtFixedPoint &= resolveEdge(edgeExpression);
+                }
+            }
+        }
+    }
+
+    private boolean resolveEdge(EdgePatternExpr edgePatternExpr) {
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        if (edgeDescriptor.getEdgeClass() == IGraphExpr.GraphExprKind.PATH_PATTERN) {
+            VertexPatternExpr workingLeftVertex = edgePatternExpr.getLeftVertex();
+
+            // We have a sub-path. Recurse with the edges of this sub-path.
+            boolean intermediateResult = true;
+            for (int i = 0; i < edgeDescriptor.getMaximumHops(); i++) {
+                VertexPatternExpr rightVertex;
+                if (i == edgeDescriptor.getMaximumHops() - 1) {
+                    // This is the final vertex in our path.
+                    rightVertex = edgePatternExpr.getRightVertex();
+
+                } else {
+                    // We need to get an intermediate vertex.
+                    rightVertex = edgePatternExpr.getInternalVertices().get(i);
+                }
+
+                // Build our EDGE-PATTERN-EXPR and recurse.
+                EdgeDescriptor newDescriptor = new EdgeDescriptor(edgeDescriptor.getEdgeType(),
+                        IGraphExpr.GraphExprKind.EDGE_PATTERN, edgeDescriptor.getEdgeLabels(), null, null, null);
+                intermediateResult &= resolveEdge(new EdgePatternExpr(workingLeftVertex, rightVertex, newDescriptor));
+
+                // Update the labels of our edge and our internal vertex.
+                edgeDescriptor.getEdgeLabels().addAll(newDescriptor.getEdgeLabels());
+                if (i != edgeDescriptor.getMaximumHops() - 1) {
+                    for (ElementLabel label : rightVertex.getLabels()) {
+                        // Mark our labels as not inferred to prevent invalidation of what we just found.
+                        label.markInferred(false);
+                    }
+                }
+                workingLeftVertex = rightVertex;
+            }
+
+            return intermediateResult;
+        }
+
+        if (edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.UNDIRECTED) {
+            // We have an undirected edge. Recurse with a LEFT_TO_RIGHT edge...
+            edgeDescriptor.setEdgeType(EdgeDescriptor.EdgeType.LEFT_TO_RIGHT);
+            boolean isLeftToRightModified = !resolveEdge(edgePatternExpr);
+
+            // ...and a RIGHT_TO_LEFT edge.
+            edgeDescriptor.setEdgeType(EdgeDescriptor.EdgeType.RIGHT_TO_LEFT);
+            boolean isRightToLeftModified = !resolveEdge(edgePatternExpr);
+
+            // Determine the direction of our edge, if possible.
+            if (isLeftToRightModified && !isRightToLeftModified) {
+                edgeDescriptor.setEdgeType(EdgeDescriptor.EdgeType.LEFT_TO_RIGHT);
+
+            } else if (!isLeftToRightModified && isRightToLeftModified) {
+                edgeDescriptor.setEdgeType(EdgeDescriptor.EdgeType.RIGHT_TO_LEFT);
+
+            } else {
+                edgeDescriptor.setEdgeType(EdgeDescriptor.EdgeType.UNDIRECTED);
+            }
+            return !(isLeftToRightModified || isRightToLeftModified);
+        }
+
+        // We have a _directed_ *edge*. Determine our source and destination vertex.
+        VertexPatternExpr sourceVertexPattern, destinationVertexPattern;
+        if (edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.LEFT_TO_RIGHT) {
+            sourceVertexPattern = edgePatternExpr.getLeftVertex();
+            destinationVertexPattern = edgePatternExpr.getRightVertex();
+
+        } else { // edgeDescriptor.getEdgeType() == EdgeDescriptor.EdgeType.RIGHT_TO_LEFT
+            sourceVertexPattern = edgePatternExpr.getRightVertex();
+            destinationVertexPattern = edgePatternExpr.getLeftVertex();
+        }
+
+        // An unknown is valid iff the element is not contained within the label set and **every** label is inferred.
+        final BiFunction<Set<ElementLabel>, ElementLabel, Boolean> validUnknownPredicate =
+                (s, e) -> !s.contains(e) && s.stream().allMatch(ElementLabel::isInferred);
+        Set<ElementLabel> sourceLabels = sourceVertexPattern.getLabels();
+        Set<ElementLabel> destLabels = destinationVertexPattern.getLabels();
+        Set<ElementLabel> edgeLabels = edgeDescriptor.getEdgeLabels();
+
+        // Iterate through our knowledge table. Attempt to fill in unknowns.
+        boolean isUnknownFilled = false;
+        for (SchemaKnowledgeTable.KnowledgeRecord knowledgeRecord : schemaKnowledgeTable) {
+            ElementLabel recordSourceLabel = knowledgeRecord.getSourceVertexLabel();
+            ElementLabel recordDestLabel = knowledgeRecord.getDestVertexLabel();
+            ElementLabel recordEdgeLabel = knowledgeRecord.getEdgeLabel();
+            boolean isSourceValidUnknown = validUnknownPredicate.apply(sourceLabels, recordSourceLabel);
+            boolean isDestValidUnknown = validUnknownPredicate.apply(destLabels, recordDestLabel);
+            boolean isEdgeValidUnknown = validUnknownPredicate.apply(edgeLabels, recordEdgeLabel);
+            if (sourceLabels.contains(recordSourceLabel)) {
+                if (edgeLabels.contains(recordEdgeLabel) && isDestValidUnknown) {
+                    // Source is known, edge is known, dest is unknown.
+                    destLabels.add(recordDestLabel.asInferred());
+                    isUnknownFilled = true;
+
+                } else if (isEdgeValidUnknown && destLabels.contains(recordDestLabel)) {
+                    // Source is known, edge is unknown, dest is known.
+                    edgeLabels.add(recordEdgeLabel.asInferred());
+                    isUnknownFilled = true;
+
+                } else if (isEdgeValidUnknown && isDestValidUnknown) {
+                    // Source is known, edge is unknown, dest is unknown.
+                    destLabels.add(recordDestLabel.asInferred());
+                    edgeLabels.add(recordEdgeLabel.asInferred());
+                    isUnknownFilled = true;
+                }
+
+            } else if (edgeLabels.contains(recordEdgeLabel)) {
+                if (isSourceValidUnknown && destLabels.contains(recordDestLabel)) {
+                    // Source is unknown, edge is known, dest is known.
+                    sourceLabels.add(recordSourceLabel.asInferred());
+                    isUnknownFilled = true;
+
+                } else if (isSourceValidUnknown && isDestValidUnknown) {
+                    // Source is unknown, edge is known, dest is unknown.
+                    sourceLabels.add(recordSourceLabel.asInferred());
+                    destLabels.add(recordDestLabel.asInferred());
+                    isUnknownFilled = true;
+                }
+
+            } else if (destLabels.contains(recordDestLabel) && isSourceValidUnknown && isEdgeValidUnknown) {
+                // Source is unknown, edge is unknown, dest is known.
+                sourceLabels.add(recordSourceLabel.asInferred());
+                edgeLabels.add(recordEdgeLabel.asInferred());
+                isUnknownFilled = true;
+            }
+        }
+        return !isUnknownFilled;
+    }
+
+    @Override
+    public boolean isAtFixedPoint() {
+        return isAtFixedPoint;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java
new file mode 100644
index 0000000..4625507
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/NoResolutionResolver.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.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/resolve/QueryKnowledgeTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/QueryKnowledgeTable.java
new file mode 100644
index 0000000..23b54e4
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/QueryKnowledgeTable.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.lang.rewrites.resolve;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+/**
+ * A knowledge base of our query graph, stored as a map of vertex variable IDs to a list of vertex labels.
+ *
+ * @see InferenceBasedResolver
+ */
+public class QueryKnowledgeTable extends HashMap<Identifier, List<ElementLabel>> {
+    private static final long serialVersionUID = 1L;
+
+    public void put(VertexPatternExpr vertexPatternExpr) {
+        Identifier vertexIdentifier = vertexPatternExpr.getVariableExpr().getVar();
+        if (!vertexPatternExpr.getLabels().isEmpty()) {
+            if (!super.containsKey(vertexIdentifier)) {
+                super.put(vertexIdentifier, new ArrayList<>());
+            }
+            super.get(vertexIdentifier).addAll(vertexPatternExpr.getLabels());
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java
new file mode 100644
index 0000000..1b2984d
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/resolve/SchemaKnowledgeTable.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.resolve;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
+
+/**
+ * A collection of ground truths, derived from the graph schema (either a {@link Graph} or {@link GraphConstructor}).
+ *
+ * @see InferenceBasedResolver
+ */
+public class SchemaKnowledgeTable implements Iterable<SchemaKnowledgeTable.KnowledgeRecord> {
+    private final Set<KnowledgeRecord> knowledgeRecordSet = new HashSet<>();
+    private final Set<ElementLabel> vertexLabelSet = new HashSet<>();
+    private final Set<ElementLabel> edgeLabelSet = new HashSet<>();
+
+    public SchemaKnowledgeTable(Graph graph) {
+        graph.getGraphSchema().getVertices().stream().map(Vertex::getLabel).forEach(vertexLabelSet::add);
+        graph.getGraphSchema().getEdges().forEach(e -> {
+            vertexLabelSet.add(e.getSourceLabel());
+            vertexLabelSet.add(e.getDestinationLabel());
+            edgeLabelSet.add(e.getLabel());
+            knowledgeRecordSet.add(new KnowledgeRecord(e.getSourceLabel(), e.getDestinationLabel(), e.getLabel()));
+        });
+    }
+
+    public SchemaKnowledgeTable(GraphConstructor graphConstructor) {
+        graphConstructor.getVertexElements().forEach(v -> vertexLabelSet.add(v.getLabel()));
+        graphConstructor.getEdgeElements().forEach(e -> {
+            vertexLabelSet.add(e.getSourceLabel());
+            vertexLabelSet.add(e.getDestinationLabel());
+            edgeLabelSet.add(e.getEdgeLabel());
+            knowledgeRecordSet.add(new KnowledgeRecord(e.getSourceLabel(), e.getDestinationLabel(), e.getEdgeLabel()));
+        });
+    }
+
+    public Set<ElementLabel> getVertexLabelSet() {
+        return vertexLabelSet;
+    }
+
+    public Set<ElementLabel> getEdgeLabelSet() {
+        return edgeLabelSet;
+    }
+
+    @Override
+    public Iterator<KnowledgeRecord> iterator() {
+        return knowledgeRecordSet.iterator();
+    }
+
+    public static class KnowledgeRecord {
+        private final ElementLabel sourceVertexLabel;
+        private final ElementLabel destVertexLabel;
+        private final ElementLabel edgeLabel;
+
+        public KnowledgeRecord(ElementLabel sourceVertexLabel, ElementLabel destVertexLabel, ElementLabel edgeLabel) {
+            this.sourceVertexLabel = Objects.requireNonNull(sourceVertexLabel);
+            this.destVertexLabel = Objects.requireNonNull(destVertexLabel);
+            this.edgeLabel = Objects.requireNonNull(edgeLabel);
+        }
+
+        public ElementLabel getSourceVertexLabel() {
+            return sourceVertexLabel;
+        }
+
+        public ElementLabel getDestVertexLabel() {
+            return destVertexLabel;
+        }
+
+        public ElementLabel getEdgeLabel() {
+            return edgeLabel;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("(%s)-[%s]->(%s)", sourceVertexLabel, edgeLabel, destVertexLabel);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if (this == object) {
+                return true;
+            }
+            if (!(object instanceof KnowledgeRecord)) {
+                return false;
+            }
+            KnowledgeRecord target = (KnowledgeRecord) object;
+            return sourceVertexLabel.equals(target.sourceVertexLabel) && destVertexLabel.equals(target.destVertexLabel)
+                    && edgeLabel.equals(target.edgeLabel);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(sourceVertexLabel, destVertexLabel, edgeLabel);
+        }
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.java
new file mode 100644
index 0000000..506ee13
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/ClauseRewritingUtil.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.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
new file mode 100644
index 0000000..e289119
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/EdgeRewritingUtil.java
@@ -0,0 +1,168 @@
+/*
+ * 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
new file mode 100644
index 0000000..dd07c3e
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/util/LowerRewritingUtil.java
@@ -0,0 +1,155 @@
+/*
+ * 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.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.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.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;
+
+public final class LowerRewritingUtil {
+    public static List<FieldAccessor> buildAccessorList(Expression startingExpr, List<List<String>> fieldNames) {
+        List<FieldAccessor> fieldAccessors = new ArrayList<>();
+        for (List<String> nestedField : fieldNames) {
+            FieldAccessor workingAccessor = new FieldAccessor(startingExpr, new Identifier(nestedField.get(0)));
+            for (String field : nestedField.subList(1, nestedField.size())) {
+                workingAccessor = new FieldAccessor(workingAccessor, new Identifier(field));
+            }
+            fieldAccessors.add(workingAccessor);
+        }
+        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
new file mode 100644
index 0000000..83d35c2
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/AbstractGraphixQueryVisitor.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.graphix.lang.clause.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.GraphDropStatement;
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.lang.common.base.AbstractClause;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.sqlpp.clause.AbstractBinaryCorrelateClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+
+public class AbstractGraphixQueryVisitor extends AbstractSqlppSimpleExpressionVisitor
+        implements IGraphixLangVisitor<Expression, ILangExpression> {
+    @Override
+    public Expression visit(GraphConstructor gc, ILangExpression arg) throws CompilationException {
+        // Visit all vertices before visiting any edges.
+        for (GraphConstructor.VertexConstructor vertexConstructor : gc.getVertexElements()) {
+            vertexConstructor.accept(this, arg);
+        }
+        for (GraphConstructor.EdgeConstructor edgeConstructor : gc.getEdgeElements()) {
+            edgeConstructor.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.VertexConstructor ve, ILangExpression arg) throws CompilationException {
+        return this.visit(ve.getExpression(), arg);
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.EdgeConstructor ee, ILangExpression arg) throws CompilationException {
+        return this.visit(ee.getExpression(), arg);
+    }
+
+    @Override
+    public Expression visit(SelectBlock sb, ILangExpression arg) throws CompilationException {
+        return (sb instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) sb, arg) : super.visit(sb, arg);
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock gsb, ILangExpression arg) throws CompilationException {
+        // Traverse in the same order as a regular SELECT-BLOCK: FROM, LET/WHERE, GROUP-BY, LET/HAVING, SELECT.
+        if (gsb.hasFromGraphClause()) {
+            gsb.getFromGraphClause().accept(this, arg);
+
+        } else if (gsb.hasFromClause()) {
+            gsb.getFromClause().accept(this, arg);
+        }
+        if (gsb.hasLetWhereClauses()) {
+            for (AbstractClause clause : gsb.getLetWhereList()) {
+                clause.accept(this, arg);
+            }
+        }
+        if (gsb.hasGroupbyClause()) {
+            gsb.getGroupbyClause().accept(this, arg);
+        }
+        if (gsb.hasLetHavingClausesAfterGroupby()) {
+            for (AbstractClause clause : gsb.getLetHavingListAfterGroupby()) {
+                clause.accept(this, arg);
+            }
+        }
+        gsb.getSelectClause().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fgc, ILangExpression arg) throws CompilationException {
+        // Visit our graph constructor (if it exists), then all of our MATCH clauses, and finally our correlate clauses.
+        if (fgc.getGraphConstructor() != null) {
+            fgc.getGraphConstructor().accept(this, arg);
+        }
+        for (MatchClause matchClause : fgc.getMatchClauses()) {
+            matchClause.accept(this, arg);
+        }
+        for (AbstractBinaryCorrelateClause correlateClause : fgc.getCorrelateClauses()) {
+            correlateClause.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(MatchClause mc, ILangExpression arg) throws CompilationException {
+        for (PathPatternExpr pathPatternExpr : mc.getPathExpressions()) {
+            pathPatternExpr.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr ppe, ILangExpression arg) throws CompilationException {
+        // Visit our vertices first, then our edges.
+        for (VertexPatternExpr vertexExpression : ppe.getVertexExpressions()) {
+            vertexExpression.accept(this, arg);
+        }
+        for (EdgePatternExpr edgeExpression : ppe.getEdgeExpressions()) {
+            edgeExpression.accept(this, arg);
+        }
+        if (ppe.getVariableExpr() != null) {
+            ppe.getVariableExpr().accept(this, arg);
+        }
+        return ppe;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr epe, ILangExpression arg) throws CompilationException {
+        // We do not visit any **terminal** vertices here. These should be handled by the containing PathPatternExpr.
+        EdgeDescriptor edgeDescriptor = epe.getEdgeDescriptor();
+        if (edgeDescriptor.getVariableExpr() != null) {
+            edgeDescriptor.getVariableExpr().accept(this, arg);
+        }
+        for (VertexPatternExpr internalVertex : epe.getInternalVertices()) {
+            internalVertex.accept(this, arg);
+        }
+        return epe;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vpe, ILangExpression arg) throws CompilationException {
+        if (vpe.getVariableExpr() != null) {
+            vpe.getVariableExpr().accept(this, arg);
+        }
+        return vpe;
+    }
+
+    @Override
+    public Expression visit(GraphElementDecl gel, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(CreateGraphStatement cgs, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphDropStatement gds, 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/ElementAnalysisVisitor.java
new file mode 100644
index 0000000..1c3882c
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementAnalysisVisitor.java
@@ -0,0 +1,203 @@
+/*
+ * 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.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.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.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;
+
+/**
+ * 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.
+ * - 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 {
+    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<>();
+
+        // 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<>();
+
+        // 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<>();
+    }
+
+    // We will return instances of the following back to our caller.
+    public static class FromGraphClauseContext {
+        private final EdgeDependencyGraph edgeDependencyGraph;
+        private final List<VertexPatternExpr> danglingVertices;
+        private final List<VarIdentifier> optionalVariables;
+
+        private FromGraphClauseContext(EdgeDependencyGraph edgeDependencyGraph,
+                List<VertexPatternExpr> danglingVertices, List<VarIdentifier> optionalVariables) {
+            this.edgeDependencyGraph = edgeDependencyGraph;
+            this.danglingVertices = danglingVertices;
+            this.optionalVariables = optionalVariables;
+        }
+
+        public EdgeDependencyGraph getEdgeDependencyGraph() {
+            return edgeDependencyGraph;
+        }
+
+        public List<VertexPatternExpr> getDanglingVertices() {
+            return danglingVertices;
+        }
+
+        public List<VarIdentifier> getOptionalVariables() {
+            return optionalVariables;
+        }
+    }
+
+    // We will build new environments on each visit of a FROM-GRAPH-CLAUSE.
+    private final Map<FromGraphClause, AnalysisEnvironment> analysisEnvironmentMap = new HashMap<>();
+    private AnalysisEnvironment workingEnvironment;
+
+    private void addEdgeDependency(VarIdentifier vertexID, VarIdentifier edgeID, List<Identifier> dependencyList) {
+        if (workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
+            dependencyList.addAll(workingEnvironment.vertexEdgeMap.get(vertexID));
+            workingEnvironment.vertexEdgeMap.get(vertexID).add(edgeID);
+
+        } else {
+            List<Identifier> vertexDependencies = new ArrayList<>();
+            vertexDependencies.add(edgeID);
+            workingEnvironment.vertexEdgeMap.put(vertexID, vertexDependencies);
+        }
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        // Add to our a map a new analysis environment.
+        workingEnvironment = new AnalysisEnvironment();
+        analysisEnvironmentMap.put(fromGraphClause, workingEnvironment);
+        return super.visit(fromGraphClause, arg);
+    }
+
+    @Override
+    public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
+        // Reset to build a new graph.
+        if (matchClause.getMatchType() == MatchType.LEFTOUTER) {
+            workingEnvironment.adjacencyMaps.add(workingEnvironment.workingAdjacencyMap);
+            workingEnvironment.workingAdjacencyMap = new LinkedHashMap<>();
+            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);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr pathExpression, ILangExpression arg) throws CompilationException {
+        // Visit our edges first, then our vertices. This allows us to determine our dangling vertices.
+        for (EdgePatternExpr edgeExpression : pathExpression.getEdgeExpressions()) {
+            edgeExpression.accept(this, arg);
+        }
+        for (VertexPatternExpr vertexExpression : pathExpression.getVertexExpressions()) {
+            vertexExpression.accept(this, arg);
+        }
+        return pathExpression;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
+        VarIdentifier vertexID = vertexExpression.getVariableExpr().getVar();
+        if (!workingEnvironment.vertexEdgeMap.containsKey(vertexID)) {
+            workingEnvironment.danglingVertices.add(vertexExpression);
+        }
+        workingEnvironment.visitedVertices.add(vertexID);
+        return vertexExpression;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        VarIdentifier leftVertexID = edgeExpression.getLeftVertex().getVariableExpr().getVar();
+        VarIdentifier rightVertexID = edgeExpression.getRightVertex().getVariableExpr().getVar();
+        VarIdentifier edgeID = edgeExpression.getEdgeDescriptor().getVariableExpr().getVar();
+        List<Identifier> edgeDependencies = new ArrayList<>();
+
+        addEdgeDependency(leftVertexID, edgeID, edgeDependencies);
+        addEdgeDependency(rightVertexID, edgeID, edgeDependencies);
+        workingEnvironment.workingAdjacencyMap.put(edgeID, edgeDependencies);
+        workingEnvironment.edgePatternMap.put(edgeID, edgeExpression);
+        for (Identifier dependencyEdgeID : edgeDependencies) {
+            // Populate our map in reverse as well.
+            workingEnvironment.workingAdjacencyMap.get(dependencyEdgeID).add(edgeID);
+        }
+
+        // We ignore any internal vertices here.
+        return edgeExpression;
+    }
+
+    public Map<FromGraphClause, FromGraphClauseContext> getFromGraphClauseContextMap() {
+        return analysisEnvironmentMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> {
+            AnalysisEnvironment analysisEnvironment = e.getValue();
+            if (!analysisEnvironment.workingAdjacencyMap.isEmpty()) {
+                // We have not finished building our edge dependency subgraph.
+                analysisEnvironment.adjacencyMaps.add(analysisEnvironment.workingAdjacencyMap);
+                analysisEnvironment.workingAdjacencyMap = new LinkedHashMap<>();
+                analysisEnvironment.vertexEdgeMap.clear();
+            }
+
+            // 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);
+        }));
+    }
+}
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
new file mode 100644
index 0000000..c150b62
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementLookupTableVisitor.java
@@ -0,0 +1,164 @@
+/*
+ * 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.HashSet;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.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.parser.GraphElementBodyParser;
+import org.apache.asterix.graphix.lang.parser.GraphixParserFactory;
+import org.apache.asterix.graphix.lang.rewrites.common.ElementLookupTable;
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.metadata.entity.schema.Edge;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.struct.Identifier;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+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).
+ */
+public class ElementLookupTableVisitor extends AbstractGraphixQueryVisitor {
+    private final IWarningCollector warningCollector;
+    private final MetadataProvider metadataProvider;
+    private final GraphixParserFactory parserFactory;
+
+    private final Set<ElementLabel> referencedVertexLabels = new HashSet<>();
+    private final Set<ElementLabel> referencedEdgeLabels = new HashSet<>();
+    private final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
+
+    public ElementLookupTableVisitor(ElementLookupTable<GraphElementIdentifier> elementLookupTable,
+            MetadataProvider metadataProvider, GraphixParserFactory parserFactory, IWarningCollector warningCollector) {
+        this.warningCollector = warningCollector;
+        this.parserFactory = parserFactory;
+        this.elementLookupTable = elementLookupTable;
+        this.metadataProvider = metadataProvider;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        for (MatchClause m : fromGraphClause.getMatchClauses()) {
+            m.accept(this, null);
+        }
+
+        if (fromGraphClause.getGraphConstructor() == null) {
+            // Our query refers to a named graph. Load this from our metadata.
+            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) {
+                    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 (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());
+                }
+            }
+
+        } 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.putVertexKey(identifier, vertex.getPrimaryKeyFields());
+                }
+            }
+            for (GraphConstructor.EdgeConstructor edge : graphConstructor.getEdgeElements()) {
+                if (referencedEdgeLabels.contains(edge.getEdgeLabel())) {
+                    GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier,
+                            GraphElementIdentifier.Kind.EDGE, edge.getEdgeLabel());
+                    elementLookupTable.put(identifier, new GraphElementDecl(identifier, edge.getExpression()));
+                    elementLookupTable.putEdgeKeys(identifier, edge.getSourceKeyFields(),
+                            edge.getDestinationKeyFields());
+                    elementLookupTable.putEdgeLabels(identifier, edge.getSourceLabel(), edge.getDestinationLabel());
+                }
+            }
+        }
+        return null;
+    }
+
+    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
+        if (edgeDescriptor.getEdgeLabels().isEmpty()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, edgeExpression.getSourceLocation(),
+                    "EdgePatternExpr found without labels. Elements should have been resolved earlier.");
+        }
+        referencedEdgeLabels.addAll(edgeDescriptor.getEdgeLabels());
+        for (VertexPatternExpr internalVertex : edgeExpression.getInternalVertices()) {
+            internalVertex.accept(this, arg);
+        }
+        return edgeExpression;
+    }
+
+    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
+        if (vertexExpression.getLabels().isEmpty()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, vertexExpression.getSourceLocation(),
+                    "VertexPatternExpr found without labels. Elements should have been resolved earlier.");
+        }
+        referencedVertexLabels.addAll(vertexExpression.getLabels());
+        return vertexExpression;
+    }
+}
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/ElementResolutionVisitor.java
new file mode 100644
index 0000000..4f3c6f9
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ElementResolutionVisitor.java
@@ -0,0 +1,181 @@
+/*
+ * 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.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.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.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.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.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);
+
+    // 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;
+
+    public ElementResolutionVisitor(MetadataProvider metadataProvider, IWarningCollector warningCollector) {
+        this.metadataProvider = metadataProvider;
+        this.warningCollector = warningCollector;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        // Establish our schema knowledge.
+        SchemaKnowledgeTable schemaKnowledgeTable;
+        if (fromGraphClause.getGraphConstructor() == null) {
+            DataverseName dataverseName = (fromGraphClause.getDataverseName() == null)
+                    ? metadataProvider.getDefaultDataverseName() : fromGraphClause.getDataverseName();
+            Identifier graphName = fromGraphClause.getGraphName();
+
+            // Fetch the graph from our metadata.
+            try {
+                Graph 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.");
+                }
+                schemaKnowledgeTable = new SchemaKnowledgeTable(graphFromMetadata);
+
+            } catch (AlgebricksException e) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, fromGraphClause.getSourceLocation(),
+                        "Graph " + graphName.getValue() + " does not exist.");
+            }
+
+        } else {
+            schemaKnowledgeTable = new SchemaKnowledgeTable(fromGraphClause.getGraphConstructor());
+        }
+
+        // Determine our resolution strategy. By default, we will perform bare-bones resolution.
+        IGraphElementResolver graphElementResolver;
+        String resolverMetadataKeyName = GraphixCompilationProvider.RESOLVER_METADATA_CONFIG;
+        if (metadataProvider.getConfig().containsKey(resolverMetadataKeyName)) {
+            String resolverProperty = metadataProvider.getProperty(resolverMetadataKeyName);
+            if (resolverProperty.equalsIgnoreCase(NoResolutionResolver.METADATA_CONFIG_NAME)) {
+                graphElementResolver = new NoResolutionResolver();
+
+            } else if (resolverProperty.equalsIgnoreCase(InferenceBasedResolver.METADATA_CONFIG_NAME)) {
+                graphElementResolver = new InferenceBasedResolver(schemaKnowledgeTable);
+
+            } else {
+                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, resolverProperty);
+            }
+
+        } else {
+            graphElementResolver = new InferenceBasedResolver(schemaKnowledgeTable);
+        }
+
+        // Perform our resolution passes (repeat until we reach a fixed point or the iteration max).
+        String resolverIterationMaxMetadataKeyName = GraphixCompilationProvider.RESOLVER_ITERATION_MAX_METADATA_CONFIG;
+        long resolverIterationMax;
+        if (metadataProvider.getConfig().containsKey(resolverIterationMaxMetadataKeyName)) {
+            String resolverIterationMaxProperty = metadataProvider.getProperty(resolverIterationMaxMetadataKeyName);
+            try {
+                resolverIterationMax = Long.parseLong(resolverIterationMaxProperty);
+
+            } catch (NumberFormatException e) {
+                throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, resolverIterationMaxProperty);
+            }
+
+        } else {
+            resolverIterationMax = DEFAULT_RESOLVER_ITERATION_MAX;
+        }
+        for (int i = 0; i < resolverIterationMax && !graphElementResolver.isAtFixedPoint(); i++) {
+            graphElementResolver.resolve(fromGraphClause);
+            if (i == resolverIterationMax - 1) {
+                LOGGER.warn("Number of iterations for element resolution has exceeded " + resolverIterationMax);
+            }
+        }
+
+        // Perform the final pass of our FROM-GRAPH-CLAUSE. Raise warnings if necessary.
+        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;
+            }
+
+            @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);
+                }
+                return edgePatternExpr;
+            }
+        }.visit(fromGraphClause, null);
+
+        return null;
+    }
+}
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
new file mode 100644
index 0000000..5a06aa4
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GenerateVariableVisitor.java
@@ -0,0 +1,67 @@
+/*
+ * 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/GraphixFunctionCallVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixFunctionCallVisitor.java
new file mode 100644
index 0000000..488acf6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixFunctionCallVisitor.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.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.GraphixFunctionResolver;
+import org.apache.asterix.graphix.function.rewrite.IFunctionRewrite;
+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.common.statement.FunctionDecl;
+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).
+ *
+ * @see GraphixFunctionIdentifiers
+ * @see GraphixFunctionResolver
+ * @see FunctionRewriteMap
+ */
+public class GraphixFunctionCallVisitor extends AbstractGraphixQueryVisitor {
+    private final GraphixRewritingContext graphixRewritingContext;
+    private final GraphixFunctionResolver graphixFunctionResolver;
+
+    public GraphixFunctionCallVisitor(GraphixRewritingContext graphixRewritingContext) {
+        this.graphixRewritingContext = graphixRewritingContext;
+
+        // We want to make sure that user-defined functions take precedence over Graphix-defined functions.
+        Map<FunctionSignature, FunctionDecl> declaredFunctions = graphixRewritingContext.getDeclaredFunctions();
+        MetadataProvider metadataProvider = graphixRewritingContext.getMetadataProvider();
+        this.graphixFunctionResolver = new GraphixFunctionResolver(metadataProvider, declaredFunctions);
+    }
+
+    @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) {
+            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);
+        if (rewrittenCallArgExpr == null) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, callExpr.getSourceLocation(),
+                    "Function " + functionIdentifier.getName() + " not implemented!");
+        }
+        return rewrittenCallArgExpr;
+    }
+}
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
new file mode 100644
index 0000000..d165bb8
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/GraphixLoweringVisitor.java
@@ -0,0 +1,254 @@
+/*
+ * 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.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+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.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.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.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.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.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;
+
+/**
+ * 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
+ */
+public class GraphixLoweringVisitor extends AbstractGraphixQueryVisitor {
+    private final Map<FromGraphClause, FromGraphClauseContext> fromGraphClauseContextMap;
+    private final ElementLookupTable<GraphElementIdentifier> elementLookupTable;
+    private final GraphixRewritingContext graphixRewritingContext;
+
+    // In addition to the parent GRAPH-SELECT-BLOCK, we must keep track of the parent SELECT-EXPR.
+    private SelectExpression selectExpression;
+
+    public GraphixLoweringVisitor(Map<FromGraphClause, FromGraphClauseContext> fromGraphClauseContextMap,
+            ElementLookupTable<GraphElementIdentifier> elementLookupTable,
+            GraphixRewritingContext graphixRewritingContext) {
+        this.fromGraphClauseContextMap = fromGraphClauseContextMap;
+        this.elementLookupTable = elementLookupTable;
+        this.graphixRewritingContext = graphixRewritingContext;
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
+        boolean hadFromGraphClause = false;
+        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;
+    }
+
+    @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);
+    }
+
+    @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);
+        }
+
+        // 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;
+            }
+
+            @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 (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.");
+            }
+        }
+
+        // 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()));
+        return null;
+    }
+}
\ No newline at end of file
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 2b41511..723d4a5 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
@@ -19,7 +19,13 @@
 package org.apache.asterix.graphix.lang.rewrites.visitor;
 
 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.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
@@ -28,13 +34,25 @@
 public interface IGraphixLangVisitor<R, T> extends ILangVisitor<R, T> {
     R visit(GraphConstructor gc, T arg) throws CompilationException;
 
-    R visit(GraphConstructor.VertexElement ve, T arg) throws CompilationException;
+    R visit(GraphConstructor.VertexConstructor ve, T arg) throws CompilationException;
 
-    R visit(GraphConstructor.EdgeElement ee, T arg) throws CompilationException;
+    R visit(GraphConstructor.EdgeConstructor ee, T arg) throws CompilationException;
 
     R visit(CreateGraphStatement cgs, T arg) throws CompilationException;
 
     R visit(GraphElementDecl gel, T arg) throws CompilationException;
 
     R visit(GraphDropStatement gds, T arg) throws CompilationException;
+
+    R visit(GraphSelectBlock gsb, T arg) throws CompilationException;
+
+    R visit(FromGraphClause fgc, T arg) throws CompilationException;
+
+    R visit(MatchClause mc, T arg) throws CompilationException;
+
+    R visit(EdgePatternExpr epe, T arg) throws CompilationException;
+
+    R visit(PathPatternExpr ppe, T arg) throws CompilationException;
+
+    R visit(VertexPatternExpr vpe, T arg) throws CompilationException;
 }
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java
new file mode 100644
index 0000000..1b8bd4b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/LabelConsistencyVisitor.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.resolve.QueryKnowledgeTable;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.struct.Identifier;
+
+/**
+ * Unify the labels across vertices of the same variable name.
+ *
+ * @see org.apache.asterix.graphix.lang.rewrites.resolve.InferenceBasedResolver
+ */
+public class LabelConsistencyVisitor extends AbstractGraphixQueryVisitor {
+    private final QueryKnowledgeTable queryKnowledgeTable;
+
+    public LabelConsistencyVisitor(QueryKnowledgeTable queryKnowledgeTable) {
+        this.queryKnowledgeTable = queryKnowledgeTable;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
+        if (vertexPatternExpr.getLabels().isEmpty()) {
+            Identifier vertexIdentifier = vertexPatternExpr.getVariableExpr().getVar();
+            if (queryKnowledgeTable.containsKey(vertexIdentifier)) {
+                vertexPatternExpr.getLabels().addAll(queryKnowledgeTable.get(vertexIdentifier));
+            }
+        }
+        return vertexPatternExpr;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java
new file mode 100644
index 0000000..c184cb5
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PostRewriteCheckVisitor.java
@@ -0,0 +1,156 @@
+/*
+ * 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.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.GraphSelectBlock;
+import org.apache.asterix.graphix.lang.clause.MatchClause;
+import org.apache.asterix.graphix.lang.expression.EdgePatternExpr;
+import org.apache.asterix.graphix.lang.expression.GraphConstructor;
+import org.apache.asterix.graphix.lang.expression.PathPatternExpr;
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
+import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.sqlpp.clause.FromTerm;
+import org.apache.asterix.lang.sqlpp.clause.JoinClause;
+import org.apache.asterix.lang.sqlpp.clause.NestClause;
+import org.apache.asterix.lang.sqlpp.clause.UnnestClause;
+import org.apache.asterix.lang.sqlpp.expression.WindowExpression;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppSimpleExpressionVisitor;
+
+/**
+ * Throw an error if a graph AST node (that isn't a special instance of {@link FromGraphClause}) is ever encountered.
+ */
+public class PostRewriteCheckVisitor extends AbstractSqlppSimpleExpressionVisitor
+        implements IGraphixLangVisitor<Expression, ILangExpression> {
+    @Override
+    public Expression visit(GraphConstructor gc, ILangExpression arg) throws CompilationException {
+        return throwException(gc);
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.VertexConstructor ve, ILangExpression arg) throws CompilationException {
+        return throwException(ve);
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.EdgeConstructor ee, ILangExpression arg) throws CompilationException {
+        return throwException(ee);
+    }
+
+    @Override
+    public Expression visit(CreateGraphStatement cgs, ILangExpression arg) throws CompilationException {
+        return throwException(cgs);
+    }
+
+    @Override
+    public Expression visit(GraphElementDecl gel, ILangExpression arg) throws CompilationException {
+        return throwException(gel);
+    }
+
+    @Override
+    public Expression visit(GraphDropStatement gds, ILangExpression arg) throws CompilationException {
+        return throwException(gds);
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock gsb, ILangExpression arg) throws CompilationException {
+        // The only Graph AST node that should survive is the GRAPH-SELECT-BLOCK, which should functionally act the same
+        // as its parent class SELECT-BLOCK.
+        if (!gsb.hasFromClause() || gsb.hasFromGraphClause()) {
+            return throwException(gsb);
+
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fgc, ILangExpression arg) throws CompilationException {
+        return throwException(fgc);
+    }
+
+    @Override
+    public Expression visit(MatchClause mc, ILangExpression arg) throws CompilationException {
+        return throwException(mc);
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr epe, ILangExpression arg) throws CompilationException {
+        return throwException(epe);
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr ppe, ILangExpression arg) throws CompilationException {
+        return throwException(ppe);
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vpe, ILangExpression arg) throws CompilationException {
+        return throwException(vpe);
+    }
+
+    // There are a few expressions that skip some sub-expressions that we need to visit...
+    @Override
+    public Expression visit(FromTerm ft, ILangExpression arg) throws CompilationException {
+        ft.getLeftVariable().accept(this, arg);
+        return super.visit(ft, arg);
+    }
+
+    @Override
+    public Expression visit(JoinClause jc, ILangExpression arg) throws CompilationException {
+        jc.getRightVariable().accept(this, arg);
+        return super.visit(jc, arg);
+    }
+
+    @Override
+    public Expression visit(NestClause nc, ILangExpression arg) throws CompilationException {
+        nc.getRightVariable().accept(this, arg);
+        return super.visit(nc, arg);
+    }
+
+    @Override
+    public Expression visit(UnnestClause uc, ILangExpression arg) throws CompilationException {
+        uc.getRightVariable().accept(this, arg);
+        return super.visit(uc, arg);
+    }
+
+    @Override
+    public Expression visit(LetClause lc, ILangExpression arg) throws CompilationException {
+        lc.getVarExpr().accept(this, arg);
+        return super.visit(lc, arg);
+    }
+
+    @Override
+    public Expression visit(WindowExpression we, ILangExpression arg) throws CompilationException {
+        we.getWindowVar().accept(this, arg);
+        return super.visit(we, arg);
+    }
+
+    private Expression throwException(ILangExpression e) throws CompilationException {
+        throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, e.getSourceLocation(),
+                e.getClass().getName() + " was encountered. Check has failed.");
+    }
+}
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
new file mode 100644
index 0000000..0033fee
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/PreRewriteCheckVisitor.java
@@ -0,0 +1,239 @@
+/*
+ * 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.HashSet;
+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.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.IGraphExpr;
+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.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Schema;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+import org.apache.asterix.lang.common.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;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+
+/**
+ * A pre-rewrite pass to validate our user query. We validate the following:
+ * 1. An edge variable is not defined more than once. (e.g. (u)-[e]-(v), (w)-[e]-(y))
+ * 2. A vertex variable is not defined more than once with labels. (e.g. (u:User)-[e]-(v), (u:User)-[]-(:Review))
+ * 3. An edge label exists in the context of the edge's {@link FromGraphClause}.
+ * 4. A vertex label exists in the context of the vertex's {@link FromGraphClause}.
+ * 5. The minimum hops and maximum hops of a sub-path is not equal to zero.
+ * 6. The maximum hops of a sub-path is greater than or equal to the minimum hops of the same sub-path.
+ * 7. An anonymous graph passes the same validation that a named graph does.
+ */
+public class PreRewriteCheckVisitor extends AbstractGraphixQueryVisitor {
+    private final MetadataProvider metadataProvider;
+
+    // Build new environments on each FROM-GRAPH-CLAUSE visit.
+    private static class PreRewriteCheckEnvironment {
+        private final Set<ElementLabel> vertexLabels = new HashSet<>();
+        private final Set<ElementLabel> edgeLabels = new HashSet<>();
+        private final Set<Identifier> vertexVariablesWithLabels = new HashSet<>();
+        private final Set<Identifier> edgeVariables = new HashSet<>();
+    }
+
+    private final Map<ILangExpression, PreRewriteCheckEnvironment> environmentMap = new HashMap<>();
+
+    public PreRewriteCheckVisitor(LangRewritingContext langRewritingContext) {
+        this.metadataProvider = langRewritingContext.getMetadataProvider();
+    }
+
+    @Override
+    public Expression visit(GraphConstructor graphConstructor, ILangExpression arg) throws CompilationException {
+        DataverseName dataverseName = metadataProvider.getDefaultDataverseName();
+        GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphConstructor.getInstanceID());
+        Schema.Builder schemaBuilder = new Schema.Builder(graphIdentifier);
+
+        // 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) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertex.getSourceLocation(),
+                        "Conflicting primary keys for vertices with label " + 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!");
+            }
+        }
+        for (GraphConstructor.EdgeConstructor edge : graphConstructor.getEdgeElements()) {
+            schemaBuilder.addEdge(edge.getEdgeLabel(), edge.getDestinationLabel(), edge.getSourceLabel(),
+                    edge.getDestinationKeyFields(), edge.getSourceKeyFields(), edge.getDefinition());
+            switch (schemaBuilder.getLastError()) {
+                case NO_ERROR:
+                    continue;
+
+                case SOURCE_VERTEX_NOT_FOUND:
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
+                            "Source vertex " + edge.getSourceLabel() + " not found in the edge " + edge.getEdgeLabel()
+                                    + ".");
+
+                case DESTINATION_VERTEX_NOT_FOUND:
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
+                            "Destination vertex " + edge.getDestinationLabel() + " not found in the edge "
+                                    + edge.getEdgeLabel() + ".");
+
+                case CONFLICTING_SOURCE_KEY:
+                case CONFLICTING_DESTINATION_KEY:
+                    throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
+                            "Conflicting edge with the same label found: " + edge.getEdgeLabel());
+
+                default:
+                    throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, edge.getSourceLocation(),
+                            "Edge constructor was not returned, and an unexpected error encountered");
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        environmentMap.put(fromGraphClause, new PreRewriteCheckEnvironment());
+
+        // Establish the vertex and edge labels associated with this FROM-GRAPH-CLAUSE.
+        if (fromGraphClause.getGraphConstructor() == 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) {
+                    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)
+                    .forEach(environmentMap.get(fromGraphClause).vertexLabels::add);
+            fromGraphClause.getGraphConstructor().getEdgeElements().forEach(e -> {
+                environmentMap.get(fromGraphClause).vertexLabels.add(e.getSourceLabel());
+                environmentMap.get(fromGraphClause).vertexLabels.add(e.getDestinationLabel());
+                environmentMap.get(fromGraphClause).edgeLabels.add(e.getEdgeLabel());
+            });
+        }
+
+        // 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);
+        }
+        for (AbstractBinaryCorrelateClause correlateClause : fromGraphClause.getCorrelateClauses()) {
+            correlateClause.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexExpression, ILangExpression arg) throws CompilationException {
+        for (ElementLabel vertexLabel : vertexExpression.getLabels()) {
+            if (!environmentMap.get(arg).vertexLabels.contains(vertexLabel)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertexExpression.getSourceLocation(),
+                        "Vertex label " + vertexLabel + " does not exist in the given graph schema.");
+            }
+        }
+        if (vertexExpression.getVariableExpr() != null && !vertexExpression.getLabels().isEmpty()) {
+            Identifier vertexIdentifier = vertexExpression.getVariableExpr().getVar();
+            if (environmentMap.get(arg).vertexVariablesWithLabels.contains(vertexIdentifier)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, vertexExpression.getSourceLocation(),
+                        "Vertex " + vertexIdentifier + " defined with a label more than once. Labels can only be "
+                                + "bound to vertices once.");
+            }
+            environmentMap.get(arg).vertexVariablesWithLabels.add(vertexIdentifier);
+        }
+        return vertexExpression;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgeExpression, ILangExpression arg) throws CompilationException {
+        EdgeDescriptor edgeDescriptor = edgeExpression.getEdgeDescriptor();
+        if (environmentMap.get(arg).edgeLabels.isEmpty()) {
+            throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
+                    "Query edge given, but no edge is defined in the schema.");
+        }
+
+        for (ElementLabel edgeLabel : edgeDescriptor.getEdgeLabels()) {
+            if (!environmentMap.get(arg).edgeLabels.contains(edgeLabel)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
+                        "Edge label " + edgeLabel + " does not exist in the given graph schema.");
+            }
+        }
+        if (edgeDescriptor.getVariableExpr() != null) {
+            Identifier edgeIdentifier = edgeDescriptor.getVariableExpr().getVar();
+            if (environmentMap.get(arg).edgeVariables.contains(edgeIdentifier)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
+                        "Edge " + edgeIdentifier + " defined more than once. Edges can only connect two vertices.");
+            }
+            environmentMap.get(arg).edgeVariables.add(edgeIdentifier);
+        }
+        if (edgeDescriptor.getEdgeClass() == IGraphExpr.GraphExprKind.PATH_PATTERN) {
+            Integer minimumHops = edgeDescriptor.getMinimumHops();
+            Integer maximumHops = edgeDescriptor.getMaximumHops();
+            if (maximumHops == 0 || (minimumHops != null && minimumHops == 0)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
+                        "Sub-path edges cannot have a hop length less than 1.");
+
+            } else if (minimumHops != null && (maximumHops < minimumHops)) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, edgeExpression.getSourceLocation(),
+                        "Sub-path edges cannot have a maximum hop length (" + maximumHops
+                                + ") less than the minimum hop length (" + minimumHops + ").");
+            }
+        }
+        return edgeExpression;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java
new file mode 100644
index 0000000..d56419b
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/QueryKnowledgeVisitor.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import org.apache.asterix.graphix.lang.expression.VertexPatternExpr;
+import org.apache.asterix.graphix.lang.rewrites.resolve.QueryKnowledgeTable;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
+
+/**
+ * @see QueryKnowledgeTable
+ */
+public class QueryKnowledgeVisitor extends AbstractGraphixQueryVisitor {
+    private final QueryKnowledgeTable queryKnowledgeTable = new QueryKnowledgeTable();
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) {
+        queryKnowledgeTable.put(vertexPatternExpr);
+        return vertexPatternExpr;
+    }
+
+    public QueryKnowledgeTable getQueryKnowledgeTable() {
+        return queryKnowledgeTable;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
new file mode 100644
index 0000000..24b8625
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/rewrites/visitor/ScopingCheckVisitor.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.rewrites.visitor;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.graphix.lang.clause.FromGraphClause;
+import org.apache.asterix.graphix.lang.clause.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.GraphDropStatement;
+import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+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;
+import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
+import org.apache.asterix.lang.sqlpp.clause.SelectClause;
+import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
+import org.apache.asterix.lang.sqlpp.util.SqlppVariableUtil;
+import org.apache.asterix.lang.sqlpp.visitor.base.AbstractSqlppExpressionScopingVisitor;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.commons.lang3.mutable.MutableObject;
+
+/**
+ * Ensure that any subtree whose immediate parent node includes a {@link FromGraphClause} follow Graphix-specific
+ * variable resolution rules (i.e. do not rely on context variables).
+ * - {@link PathPatternExpr} may introduce a variable. Uniqueness is enforced here.
+ * - {@link EdgePatternExpr} may introduce a variable. Uniqueness is enforced w/ {@link PreRewriteCheckVisitor}.
+ * - {@link VertexPatternExpr} may introduce a variable. Uniqueness is not required.
+ * - {@link org.apache.asterix.lang.sqlpp.clause.JoinClause} may introduce a variable (handled in parent).
+ * - {@link org.apache.asterix.lang.sqlpp.clause.NestClause} may introduce a variable (handled in parent).
+ * - {@link org.apache.asterix.lang.sqlpp.clause.UnnestClause} may introduce a variable (handled in parent).
+ * - {@link org.apache.asterix.lang.common.clause.GroupbyClause} may introduce a variable (handled in parent).
+ * - {@link org.apache.asterix.lang.common.clause.LetClause} may introduce a variable (handled in parent).
+ */
+public class ScopingCheckVisitor extends AbstractSqlppExpressionScopingVisitor
+        implements IGraphixLangVisitor<Expression, ILangExpression> {
+    private final Deque<Mutable<Boolean>> graphixVisitStack = new ArrayDeque<>();
+
+    public ScopingCheckVisitor(LangRewritingContext context) {
+        super(context);
+
+        // We start with an element of false in our stack.
+        graphixVisitStack.addLast(new MutableObject<>(false));
+    }
+
+    @Override
+    public Expression visit(SelectExpression selectExpression, ILangExpression arg) throws CompilationException {
+        graphixVisitStack.addLast(new MutableObject<>(false));
+        super.visit(selectExpression, arg);
+        graphixVisitStack.removeLast();
+        return selectExpression;
+    }
+
+    @Override
+    public Expression visit(SelectBlock selectBlock, ILangExpression arg) throws CompilationException {
+        return (selectBlock instanceof GraphSelectBlock) ? this.visit((GraphSelectBlock) selectBlock, arg)
+                : super.visit(selectBlock, arg);
+    }
+
+    @Override
+    public Expression visit(GraphSelectBlock graphSelectBlock, ILangExpression arg) throws CompilationException {
+        graphixVisitStack.getLast().setValue(true);
+        if (graphSelectBlock.hasFromGraphClause()) {
+            graphSelectBlock.getFromGraphClause().accept(this, arg);
+
+        } else if (graphSelectBlock.hasFromClause()) {
+            graphSelectBlock.getFromClause().accept(this, arg);
+        }
+        if (graphSelectBlock.hasLetWhereClauses()) {
+            for (AbstractClause clause : graphSelectBlock.getLetWhereList()) {
+                clause.accept(this, arg);
+            }
+        }
+        if (graphSelectBlock.hasGroupbyClause()) {
+            graphSelectBlock.getGroupbyClause().accept(this, arg);
+        }
+        if (graphSelectBlock.hasLetHavingClausesAfterGroupby()) {
+            for (AbstractClause clause : graphSelectBlock.getLetHavingListAfterGroupby()) {
+                clause.accept(this, arg);
+            }
+        }
+        graphSelectBlock.getSelectClause().accept(this, arg);
+        return null;
+    }
+
+    @Override
+    public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) throws CompilationException {
+        // We are now working with a new scope.
+        scopeChecker.createNewScope();
+        for (MatchClause matchClause : fromGraphClause.getMatchClauses()) {
+            matchClause.accept(this, arg);
+        }
+        for (AbstractBinaryCorrelateClause correlateClause : fromGraphClause.getCorrelateClauses()) {
+            correlateClause.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(MatchClause matchClause, ILangExpression arg) throws CompilationException {
+        for (PathPatternExpr pathPatternExpr : matchClause.getPathExpressions()) {
+            pathPatternExpr.accept(this, arg);
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(PathPatternExpr pathPatternExpr, ILangExpression arg) throws CompilationException {
+        // Visit our vertices first, then our edges.
+        for (VertexPatternExpr vertexExpression : pathPatternExpr.getVertexExpressions()) {
+            vertexExpression.accept(this, arg);
+        }
+        for (EdgePatternExpr edgeExpression : pathPatternExpr.getEdgeExpressions()) {
+            edgeExpression.accept(this, arg);
+        }
+
+        // Ensure that we don't have a duplicate alias here.
+        VariableExpr pathVariable = pathPatternExpr.getVariableExpr();
+        if (pathVariable != null) {
+            String pathVariableValue = pathVariable.getVar().getValue();
+            if (scopeChecker.getCurrentScope().findLocalSymbol(pathVariableValue) != null) {
+                throw new CompilationException(ErrorCode.COMPILATION_ERROR, pathVariable.getSourceLocation(),
+                        "Duplicate alias definitions: " + SqlppVariableUtil.toUserDefinedName(pathVariableValue));
+            }
+            scopeChecker.getCurrentScope().addNewVarSymbolToScope(pathVariable.getVar());
+        }
+        return pathPatternExpr;
+    }
+
+    @Override
+    public Expression visit(EdgePatternExpr edgePatternExpr, ILangExpression arg) throws CompilationException {
+        // We do not visit any **terminal** vertices here.
+        EdgeDescriptor edgeDescriptor = edgePatternExpr.getEdgeDescriptor();
+        if (edgeDescriptor.getVariableExpr() != null) {
+            scopeChecker.getCurrentScope().addNewVarSymbolToScope(edgeDescriptor.getVariableExpr().getVar());
+        }
+        return edgePatternExpr;
+    }
+
+    @Override
+    public Expression visit(VertexPatternExpr vertexPatternExpr, ILangExpression arg) throws CompilationException {
+        if (vertexPatternExpr.getVariableExpr() != null) {
+            scopeChecker.getCurrentScope().addNewVarSymbolToScope(vertexPatternExpr.getVariableExpr().getVar());
+        }
+        return vertexPatternExpr;
+    }
+
+    // We aren't going to inline our column aliases (yet), so add our select variables to our scope.
+    @Override
+    public Expression visit(SelectClause selectClause, ILangExpression arg) throws CompilationException {
+        super.visit(selectClause, arg);
+        if (selectClause.selectRegular()) {
+            SelectRegular selectRegular = selectClause.getSelectRegular();
+            for (Projection projection : selectRegular.getProjections()) {
+                String variableName = SqlppVariableUtil.toInternalVariableName(projection.getName());
+                scopeChecker.getCurrentScope().addSymbolToScope(new Identifier(variableName), Set.of());
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Expression visit(VariableExpr varExpr, ILangExpression arg) throws CompilationException {
+        boolean hasVisitedGraphixNode = !graphixVisitStack.isEmpty() && graphixVisitStack.getLast().getValue();
+        String varSymbol = varExpr.getVar().getValue();
+
+        // We will only throw an unresolved error if we first encounter a Graphix AST node.
+        if (hasVisitedGraphixNode && scopeChecker.getCurrentScope().findSymbol(varSymbol) == null) {
+            throw new CompilationException(ErrorCode.UNDEFINED_IDENTIFIER, varExpr.getSourceLocation(),
+                    SqlppVariableUtil.toUserDefinedVariableName(varSymbol).getValue());
+        }
+        return varExpr;
+    }
+
+    // We leave the scoping of our GRAPH-CONSTRUCTOR bodies to our body rewriter.
+    @Override
+    public Expression visit(GraphConstructor graphConstructor, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.VertexConstructor vertexConstructor, ILangExpression arg)
+            throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphConstructor.EdgeConstructor edgeConstructor, ILangExpression arg)
+            throws CompilationException {
+        return null;
+    }
+
+    // The following should not appear in queries.
+    @Override
+    public Expression visit(CreateGraphStatement createGraphStatement, ILangExpression arg)
+            throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphElementDecl graphElementDecl, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+
+    @Override
+    public Expression visit(GraphDropStatement graphDropStatement, ILangExpression arg) throws CompilationException {
+        return null;
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
index 866e2f2..730db34 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/CreateGraphStatement.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.graphix.lang.statement;
 
 import java.util.List;
+import java.util.Objects;
 
 import org.apache.asterix.algebra.extension.ExtensionStatement;
 import org.apache.asterix.app.translator.QueryTranslator;
@@ -34,6 +35,13 @@
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 
+/**
+ * Statement for storing a {@link GraphConstructor} instance in our metadata.
+ * - A CREATE GRAPH statement MUST always include a graph name.
+ * - We can specify "CREATE OR REPLACE" to perform an upsert of our graph.
+ * - We can specify "CREATE ... IF NOT EXISTS" to insert the graph if it doesn't exist, and not raise an error if the
+ * graph already exists.
+ */
 public class CreateGraphStatement extends ExtensionStatement {
     private final GraphConstructor graphConstructor;
     private final DataverseName dataverseName;
@@ -44,7 +52,7 @@
     public CreateGraphStatement(DataverseName dataverseName, String graphName, boolean replaceIfExists,
             boolean ifNotExists, GraphConstructor graphConstructor) {
         this.dataverseName = dataverseName;
-        this.graphName = graphName;
+        this.graphName = Objects.requireNonNull(graphName);
         this.replaceIfExists = replaceIfExists;
         this.ifNotExists = ifNotExists;
         this.graphConstructor = graphConstructor;
@@ -66,11 +74,11 @@
         return ifNotExists;
     }
 
-    public List<GraphConstructor.VertexElement> getVertexElements() {
+    public List<GraphConstructor.VertexConstructor> getVertexElements() {
         return graphConstructor.getVertexElements();
     }
 
-    public List<GraphConstructor.EdgeElement> getEdgeElements() {
+    public List<GraphConstructor.EdgeConstructor> getEdgeElements() {
         return graphConstructor.getEdgeElements();
     }
 
@@ -94,7 +102,7 @@
             IRequestParameters requestParameters, MetadataProvider metadataProvider, int resultSetId) throws Exception {
         metadataProvider.validateDatabaseObjectName(dataverseName, graphName, this.getSourceLocation());
         DataverseName activeDataverseName = statementExecutor.getActiveDataverseName(this.dataverseName);
-        GraphStatementHandlingUtil.acquireGraphWriteLocks(metadataProvider, activeDataverseName, graphName);
+        GraphStatementHandlingUtil.acquireGraphExtensionWriteLocks(metadataProvider, activeDataverseName, graphName);
         try {
             GraphStatementHandlingUtil.handleCreateGraph(this, metadataProvider, statementExecutor,
                     activeDataverseName);
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
index e9e8255..9aadb97 100644
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/statement/GraphDropStatement.java
@@ -18,6 +18,8 @@
  */
 package org.apache.asterix.graphix.lang.statement;
 
+import java.util.Objects;
+
 import org.apache.asterix.algebra.extension.ExtensionStatement;
 import org.apache.asterix.app.translator.QueryTranslator;
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -31,6 +33,13 @@
 import org.apache.hyracks.api.client.IHyracksClientConnection;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 
+/**
+ * Statement for removing a {@link org.apache.asterix.graphix.lang.expression.GraphConstructor} instance from our
+ * metadata.
+ * - A DROP GRAPH statement MUST always include a graph name.
+ * - We can specify "DROP ... IF EXISTS" to drop the graph if it exists, and not raise an error if the graph doesn't
+ * exist.
+ */
 public class GraphDropStatement extends ExtensionStatement {
     private final DataverseName dataverseName;
     private final String graphName;
@@ -38,7 +47,7 @@
 
     public GraphDropStatement(DataverseName dataverseName, String graphName, boolean ifExists) {
         this.dataverseName = dataverseName;
-        this.graphName = graphName;
+        this.graphName = Objects.requireNonNull(graphName);
         this.ifExists = ifExists;
     }
 
@@ -74,9 +83,8 @@
             IRequestParameters requestParameters, MetadataProvider metadataProvider, int resultSetId) throws Exception {
         metadataProvider.validateDatabaseObjectName(dataverseName, graphName, this.getSourceLocation());
         DataverseName activeDataverseName = statementExecutor.getActiveDataverseName(this.dataverseName);
-        GraphStatementHandlingUtil.acquireGraphWriteLocks(metadataProvider, activeDataverseName, graphName);
+        GraphStatementHandlingUtil.acquireGraphExtensionWriteLocks(metadataProvider, activeDataverseName, graphName);
         try {
-            // TODO (GLENN): Determine how to handle functions and views that depend on graphs.
             GraphStatementHandlingUtil.handleGraphDrop(this, metadataProvider, activeDataverseName);
 
         } catch (Exception e) {
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/GraphElementDecl.java
index c5e87de..5146de1 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/GraphElementDecl.java
@@ -35,6 +35,14 @@
 import org.apache.asterix.translator.IStatementExecutor;
 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.
+ */
 public final class GraphElementDecl extends ExtensionStatement {
     private final GraphElementIdentifier identifier;
     private final List<Expression> bodies = new ArrayList<>();
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
new file mode 100644
index 0000000..2fbf258
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/EdgeDescriptor.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.struct;
+
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.expression.IGraphExpr;
+import org.apache.asterix.lang.common.expression.VariableExpr;
+
+/**
+ * Descriptor for a query edge instance. A query edge has the following:
+ * 1. A set of edge labels.
+ * 2. A variable associated with the query edge.
+ * 3. An edge class. An edge can either be a pure edge, or a sub-path.
+ * 4. A minimum number of hops (allowed to be NULL, indicating a minimum of 1 hop).
+ * 5. A maximum number of hops (not allowed to be NULL).
+ * 6. An edge type, which denotes the direction (left to right, right to left, or undirected).
+ */
+public class EdgeDescriptor {
+    private final IGraphExpr.GraphExprKind edgeClass;
+    private final Set<ElementLabel> edgeLabels;
+    private final Integer minimumHops;
+    private final Integer maximumHops;
+
+    // We must be able to assign variables to our edges, as well as change the direction of UNDIRECTED edges.
+    private VariableExpr variableExpr;
+    private EdgeType edgeType;
+
+    public EdgeDescriptor(EdgeType edgeType, IGraphExpr.GraphExprKind edgeClass, Set<ElementLabel> edgeLabels,
+            VariableExpr variableExpr, Integer minimumHops, Integer maximumHops) {
+        this.edgeType = edgeType;
+        this.edgeLabels = edgeLabels;
+        this.minimumHops = minimumHops;
+        this.maximumHops = maximumHops;
+        this.edgeClass = edgeClass;
+        this.variableExpr = variableExpr;
+
+        // We enforce that an edge can take two forms: as a sub-path, and as a pure "edge".
+        switch (edgeClass) {
+            case GRAPH_CONSTRUCTOR:
+            case GRAPH_ELEMENT_BODY:
+            case VERTEX_PATTERN:
+                throw new IllegalArgumentException(
+                        "Illegal class of edge specified! An edge can only be a PATH or an EDGE.");
+        }
+    }
+
+    public EdgeType getEdgeType() {
+        return edgeType;
+    }
+
+    public void setEdgeType(EdgeType edgeType) {
+        this.edgeType = edgeType;
+    }
+
+    public Set<ElementLabel> getEdgeLabels() {
+        return edgeLabels;
+    }
+
+    public Integer getMinimumHops() {
+        return minimumHops;
+    }
+
+    public Integer getMaximumHops() {
+        return maximumHops;
+    }
+
+    public IGraphExpr.GraphExprKind getEdgeClass() {
+        return edgeClass;
+    }
+
+    public VariableExpr getVariableExpr() {
+        return variableExpr;
+    }
+
+    public void setVariableExpr(VariableExpr variableExpr) {
+        this.variableExpr = variableExpr;
+    }
+
+    public List<GraphElementIdentifier> generateIdentifiers(GraphIdentifier graphIdentifier) {
+        return edgeLabels.stream()
+                .map(e -> new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.EDGE, e))
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public String toString() {
+        String labelsString = edgeLabels.stream().map(ElementLabel::toString).collect(Collectors.joining("|"));
+        String variableString = (variableExpr != null) ? variableExpr.getVar().toString() : "";
+        String subPathString = (edgeClass != IGraphExpr.GraphExprKind.PATH_PATTERN) ? ""
+                : "{" + ((minimumHops == null) ? "" : minimumHops) + "," + maximumHops + "}";
+        return String.format("%s-[%s:(%s)%s]-%s", (edgeType == EdgeType.LEFT_TO_RIGHT) ? "" : "<", variableString,
+                labelsString, subPathString, (edgeType == EdgeType.RIGHT_TO_LEFT) ? "" : ">");
+    }
+
+    public enum EdgeType {
+        LEFT_TO_RIGHT,
+        RIGHT_TO_LEFT,
+        UNDIRECTED
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java
new file mode 100644
index 0000000..9add17a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/lang/struct/ElementLabel.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.lang.struct;
+
+import java.util.Objects;
+
+public class ElementLabel {
+    private final String labelName;
+    private boolean isInferred;
+
+    public ElementLabel(String labelName) {
+        this(labelName, false);
+    }
+
+    private ElementLabel(String labelName, boolean isInferred) {
+        this.labelName = Objects.requireNonNull(labelName);
+        this.isInferred = isInferred;
+    }
+
+    public ElementLabel asInferred() {
+        return new ElementLabel(labelName, true);
+    }
+
+    public void markInferred(boolean isInferred) {
+        this.isInferred = isInferred;
+    }
+
+    public boolean isInferred() {
+        return isInferred;
+    }
+
+    @Override
+    public String toString() {
+        return labelName;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(labelName);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o instanceof ElementLabel) {
+            ElementLabel that = (ElementLabel) o;
+            return this.labelName.equals(that.labelName);
+        }
+        return false;
+    }
+}
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 1d7d0da..9678ed8 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,8 +19,11 @@
 package org.apache.asterix.graphix.lang.util;
 
 import java.rmi.RemoteException;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 
 import org.apache.asterix.common.api.IMetadataLockManager;
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -30,14 +33,22 @@
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
 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.expression.GraphConstructor;
 import org.apache.asterix.graphix.lang.rewrites.GraphixQueryRewriter;
+import org.apache.asterix.graphix.lang.rewrites.visitor.AbstractGraphixQueryVisitor;
 import org.apache.asterix.graphix.lang.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
-import org.apache.asterix.graphix.metadata.entities.Graph;
-import org.apache.asterix.graphix.metadata.entities.GraphDependencies;
+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;
+import org.apache.asterix.graphix.metadata.entity.schema.Edge;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Schema;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
 import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.ILangExpression;
 import org.apache.asterix.metadata.MetadataManager;
 import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.declared.MetadataProvider;
@@ -46,8 +57,8 @@
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
 
 public final class GraphStatementHandlingUtil {
-    public static void acquireGraphWriteLocks(MetadataProvider metadataProvider, DataverseName activeDataverseName,
-            String graphName) throws AlgebricksException {
+    public static void acquireGraphExtensionWriteLocks(MetadataProvider metadataProvider,
+            DataverseName activeDataverseName, String graphName) throws AlgebricksException {
         // Acquire a READ lock on our dataverse and a WRITE lock on our graph.
         IMetadataLockManager metadataLockManager = metadataProvider.getApplicationContext().getMetadataLockManager();
         metadataLockManager.acquireDataverseReadLock(metadataProvider.getLocks(), activeDataverseName);
@@ -55,14 +66,44 @@
                 GraphixMetadataExtension.GRAPHIX_METADATA_EXTENSION_ID.getName(), activeDataverseName, graphName);
     }
 
+    public static void throwIfDependentExists(MetadataTransactionContext mdTxnCtx,
+            DependencyIdentifier dependencyIdentifier) throws AlgebricksException {
+        for (IEntityRequirements requirements : GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx)) {
+            for (DependencyIdentifier dependency : requirements) {
+                if (dependency.equals(dependencyIdentifier)) {
+                    throw new CompilationException(ErrorCode.CANNOT_DROP_OBJECT_DEPENDENT_EXISTS,
+                            dependency.getDependencyKind(), dependency.getDisplayName(),
+                            requirements.getDependentKind(), requirements.getDisplayName());
+                }
+            }
+        }
+    }
+
+    public static void collectDependenciesOnGraph(Expression expression, DataverseName defaultDataverseName,
+            Set<DependencyIdentifier> graphDependencies) throws CompilationException {
+        expression.accept(new AbstractGraphixQueryVisitor() {
+            @Override
+            public Expression visit(FromGraphClause fromGraphClause, ILangExpression arg) {
+                if (fromGraphClause.getGraphName() != null) {
+                    String graphName = fromGraphClause.getGraphName().getValue();
+                    DataverseName dataverseName = (fromGraphClause.getDataverseName() == null) ? defaultDataverseName
+                            : fromGraphClause.getDataverseName();
+                    DependencyIdentifier.Kind graphKind = DependencyIdentifier.Kind.GRAPH;
+                    graphDependencies.add(new DependencyIdentifier(dataverseName, graphName, graphKind));
+                }
+                return null;
+            }
+        }, null);
+    }
+
     public static void handleCreateGraph(CreateGraphStatement cgs, MetadataProvider metadataProvider,
             IStatementExecutor statementExecutor, DataverseName activeDataverseName) throws Exception {
         MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
         metadataProvider.setMetadataTxnContext(mdTxnCtx);
 
         // Ensure that our active dataverse exists.
-        Dataverse dv = MetadataManager.INSTANCE.getDataverse(mdTxnCtx, activeDataverseName);
-        if (dv == null) {
+        Dataverse dataverse = MetadataManager.INSTANCE.getDataverse(mdTxnCtx, activeDataverseName);
+        if (dataverse == null) {
             throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, cgs.getSourceLocation(), activeDataverseName);
         }
 
@@ -78,13 +119,23 @@
                         "Graph " + existingGraph.getGraphName() + " already exists.");
             }
         }
+        IEntityRequirements existingRequirements = null;
+        if (existingGraph != null) {
+            existingRequirements = GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx).stream()
+                    .filter(r -> r.getDataverseName().equals(activeDataverseName))
+                    .filter(r -> r.getEntityName().equals(cgs.getGraphName()))
+                    .filter(r -> r.getDependentKind().equals(IEntityRequirements.DependentKind.GRAPH)).findFirst()
+                    .orElseThrow(() -> new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
+                            cgs.getSourceLocation(), "Graph dependencies for " + cgs.getGraphName()
+                                    + " exist, but the graph itself does not."));
+        }
 
         // Build the graph schema.
         GraphIdentifier graphIdentifier = new GraphIdentifier(activeDataverseName, cgs.getGraphName());
-        Graph.Schema.Builder schemaBuilder = new Graph.Schema.Builder(graphIdentifier);
+        Schema.Builder schemaBuilder = new Schema.Builder(graphIdentifier);
         Map<GraphElementIdentifier, GraphElementDecl> graphElementDecls = new LinkedHashMap<>();
-        for (GraphConstructor.VertexElement vertex : cgs.getVertexElements()) {
-            Graph.Vertex schemaVertex =
+        for (GraphConstructor.VertexConstructor vertex : cgs.getVertexElements()) {
+            Vertex schemaVertex =
                     schemaBuilder.addVertex(vertex.getLabel(), vertex.getPrimaryKeyFields(), vertex.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
@@ -105,21 +156,13 @@
 
                 default:
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, vertex.getSourceLocation(),
-                            "Schema 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 primary key!");
             }
         }
-        for (GraphConstructor.EdgeElement edge : cgs.getEdgeElements()) {
-            Graph.Edge schemaEdge;
-            if (edge.getDefinition() == null) {
-                schemaEdge = schemaBuilder.addEdge(edge.getEdgeLabel(), edge.getDestinationLabel(),
-                        edge.getSourceLabel(), edge.getDestinationKeyFields());
-
-            } else {
-                schemaEdge = schemaBuilder.addEdge(edge.getEdgeLabel(), edge.getDestinationLabel(),
-                        edge.getSourceLabel(), edge.getPrimaryKeyFields(), edge.getDestinationKeyFields(),
-                        edge.getSourceKeyFields(), edge.getDefinition());
-            }
-
+        for (GraphConstructor.EdgeConstructor edge : cgs.getEdgeElements()) {
+            Edge schemaEdge =
+                    schemaBuilder.addEdge(edge.getEdgeLabel(), edge.getDestinationLabel(), edge.getSourceLabel(),
+                            edge.getDestinationKeyFields(), edge.getSourceKeyFields(), edge.getDefinition());
             switch (schemaBuilder.getLastError()) {
                 case NO_ERROR:
                     if (edge.getDefinition() != null) {
@@ -145,47 +188,51 @@
                             "Destination vertex " + edge.getDestinationLabel() + " not found in the edge "
                                     + edge.getEdgeLabel() + ".");
 
-                case CONFLICTING_PRIMARY_KEY:
-                case CONFLICTING_SOURCE_VERTEX:
-                case CONFLICTING_DESTINATION_VERTEX:
+                case CONFLICTING_SOURCE_KEY:
+                case CONFLICTING_DESTINATION_KEY:
                     throw new CompilationException(ErrorCode.COMPILATION_ERROR, edge.getSourceLocation(),
                             "Conflicting edge with the same label found: " + edge.getEdgeLabel());
 
                 default:
                     throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, edge.getSourceLocation(),
-                            "Schema edge was not returned, and an unexpected error encountered");
+                            "Edge constructor was not returned, and an unexpected error encountered");
             }
         }
 
-        // Verify that each element definition is usable.
         GraphixQueryRewriter graphixQueryRewriter = ((GraphixQueryTranslator) statementExecutor).getQueryRewriter();
-        metadataProvider.setDefaultDataverse(dv);
+        metadataProvider.setDefaultDataverse(dataverse);
+        DataverseName dataverseName = (cgs.getDataverseName() != null) ? cgs.getDataverseName() : activeDataverseName;
+        GraphRequirements requirements = new GraphRequirements(dataverseName, cgs.getGraphName());
         for (GraphElementDecl graphElementDecl : graphElementDecls.values()) {
+            // Determine the graph dependencies using the raw body.
+            Set<DependencyIdentifier> graphDependencies = new HashSet<>();
+            for (Expression rawBody : graphElementDecl.getBodies()) {
+                collectDependenciesOnGraph(rawBody, activeDataverseName, graphDependencies);
+            }
+            requirements.loadGraphDependencies(graphDependencies);
+
+            // Verify that each element definition is usable.
             ((GraphixQueryTranslator) statementExecutor).setGraphElementNormalizedBody(metadataProvider,
                     graphElementDecl, graphixQueryRewriter);
-        }
 
-        // Build our dependencies (collected over all graph element bodies).
-        GraphDependencies graphDependencies = new GraphDependencies();
-        for (GraphElementDecl graphElementDecl : graphElementDecls.values()) {
-            if (graphElementDecl.getNormalizedBodies().size() != graphElementDecl.getBodies().size()) {
-                // We should have set the normalized body by calling {@code normalizeGraphElementAsQuery} beforehand.
-                throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE,
-                        graphElementDecl.getSourceLocation(), "Normalized body not found!");
-            }
+            // Determine the non-graph dependencies using the normalized body.
             for (Expression normalizedBody : graphElementDecl.getNormalizedBodies()) {
-                graphDependencies.collectDependencies(normalizedBody, graphixQueryRewriter);
+                requirements.loadNonGraphDependencies(normalizedBody, graphixQueryRewriter);
             }
         }
 
-        // Add / upsert our graph to our metadata.
-        Graph newGraph = new Graph(graphIdentifier, schemaBuilder.build(), graphDependencies);
+        // Add / upsert our graph + requirements to our metadata.
+        Graph newGraph = new Graph(graphIdentifier, schemaBuilder.build());
         if (existingGraph == null) {
             MetadataManager.INSTANCE.addEntity(mdTxnCtx, newGraph);
+            MetadataManager.INSTANCE.addEntity(mdTxnCtx, requirements);
 
         } else {
+            requirements.setPrimaryKeyValue(existingRequirements.getPrimaryKeyValue());
             MetadataManager.INSTANCE.upsertEntity(mdTxnCtx, newGraph);
+            MetadataManager.INSTANCE.upsertEntity(mdTxnCtx, requirements);
         }
+
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
     }
 
@@ -220,6 +267,20 @@
             }
         }
 
+        // Verify that no requirements exist on our graph.
+        DataverseName dataverseName = (gds.getDataverseName() == null) ? activeDataverseName : gds.getDataverseName();
+        DependencyIdentifier dependencyIdentifier =
+                new DependencyIdentifier(dataverseName, gds.getGraphName(), DependencyIdentifier.Kind.GRAPH);
+        throwIfDependentExists(mdTxnCtx, dependencyIdentifier);
+        Optional<IEntityRequirements> requirements = GraphixMetadataExtension.getAllEntityRequirements(mdTxnCtx)
+                .stream().filter(r -> r.getDataverseName().equals(dataverseName))
+                .filter(r -> r.getEntityName().equals(gds.getGraphName()))
+                .filter(r -> r.getDependentKind().equals(IEntityRequirements.DependentKind.GRAPH)).findFirst();
+        if (requirements.isPresent()) {
+            MetadataManager.INSTANCE.deleteEntity(mdTxnCtx, requirements.get());
+        }
+
+        // Finally, perform the deletion.
         MetadataManager.INSTANCE.deleteEntity(mdTxnCtx, graph);
         MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
     }
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
new file mode 100644
index 0000000..942c0a8
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixIndexDetailProvider.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.metadata.bootstrap;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.asterix.common.metadata.MetadataIndexImmutableProperties;
+import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
+import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entitytupletranslators.DependencyTupleTranslator;
+import org.apache.asterix.graphix.metadata.entitytupletranslators.GraphTupleTranslator;
+import org.apache.asterix.metadata.api.ExtensionMetadataDataset;
+import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
+import org.apache.asterix.metadata.api.IMetadataEntityTupleTranslatorFactory;
+import org.apache.asterix.metadata.bootstrap.MetadataRecordTypes;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+
+/**
+ * Provide detail about two metadata extension indexes: Graph and GraphDependency.
+ */
+public class GraphixIndexDetailProvider {
+    public static IGraphixIndexDetail<Graph> getGraphIndexDetail() {
+        return graphIndexDetail;
+    }
+
+    public static IGraphixIndexDetail<IEntityRequirements> getGraphDependencyIndexDetail() {
+        return dependencyIndexDetail;
+    }
+
+    private static final IGraphixIndexDetail<Graph> graphIndexDetail = new IGraphixIndexDetail<>() {
+        private final ExtensionMetadataDatasetId datasetID = new ExtensionMetadataDatasetId(
+                GraphixMetadataExtension.GRAPHIX_METADATA_EXTENSION_ID, getDatasetName());
+
+        private final ExtensionMetadataDataset<Graph> metadataDataset;
+        { // Construct our metadata dataset here.
+            MetadataIndexImmutableProperties indexProperties = new MetadataIndexImmutableProperties(getDatasetName(),
+                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID,
+                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID);
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING };
+            List<List<String>> fieldNames = Arrays.asList(List.of(MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME),
+                    List.of(GraphixRecordDetailProvider.FIELD_NAME_GRAPH_NAME));
+            IRecordTypeDetail graphRecordDetail = GraphixRecordDetailProvider.getGraphRecordDetail();
+            metadataDataset = new ExtensionMetadataDataset<>(indexProperties, 3, fieldTypes, fieldNames, 0,
+                    graphRecordDetail.getRecordType(), true, new int[] { 0, 1 }, datasetID,
+                    (IMetadataEntityTupleTranslatorFactory<Graph>) GraphTupleTranslator::new);
+        }
+
+        @Override
+        public String getDatasetName() {
+            return "Graph";
+        }
+
+        @Override
+        public ExtensionMetadataDatasetId getExtensionDatasetID() {
+            return datasetID;
+        }
+
+        @Override
+        public ExtensionMetadataDataset<Graph> getExtensionDataset() {
+            return metadataDataset;
+        }
+    };
+
+    private static final IGraphixIndexDetail<IEntityRequirements> dependencyIndexDetail = new IGraphixIndexDetail<>() {
+        private final ExtensionMetadataDatasetId datasetID = new ExtensionMetadataDatasetId(
+                GraphixMetadataExtension.GRAPHIX_METADATA_EXTENSION_ID, getDatasetName());
+
+        private final ExtensionMetadataDataset<IEntityRequirements> metadataDataset;
+        { // Construct our metadata dataset here. Our primary key is autogenerated.
+            MetadataIndexImmutableProperties indexProperties = new MetadataIndexImmutableProperties(getDatasetName(),
+                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID + 1,
+                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID + 1);
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING };
+            List<List<String>> fieldNames = List.of(List.of(GraphixRecordDetailProvider.FIELD_NAME_DEPENDENCY_ID));
+            IRecordTypeDetail dependencyRecordDetail = GraphixRecordDetailProvider.getGraphDependencyRecordDetail();
+            metadataDataset = new ExtensionMetadataDataset<>(indexProperties, 2, fieldTypes, fieldNames, 0,
+                    dependencyRecordDetail.getRecordType(), true, new int[] { 0 }, datasetID,
+                    (IMetadataEntityTupleTranslatorFactory<IEntityRequirements>) DependencyTupleTranslator::new);
+        }
+
+        @Override
+        public String getDatasetName() {
+            return "GraphDependency";
+        }
+
+        @Override
+        public ExtensionMetadataDatasetId getExtensionDatasetID() {
+            return datasetID;
+        }
+
+        @Override
+        public ExtensionMetadataDataset<IEntityRequirements> getExtensionDataset() {
+            return metadataDataset;
+        }
+    };
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataIndexes.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataIndexes.java
deleted file mode 100644
index c2e7770..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataIndexes.java
+++ /dev/null
@@ -1,54 +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.metadata.bootstrap;
-
-import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
-
-import java.util.Arrays;
-import java.util.Collections;
-
-import org.apache.asterix.common.metadata.MetadataIndexImmutableProperties;
-import org.apache.asterix.graphix.extension.GraphixMetadataExtension;
-import org.apache.asterix.graphix.metadata.entities.Graph;
-import org.apache.asterix.graphix.metadata.entitytupletranslators.GraphTupleTranslator;
-import org.apache.asterix.metadata.api.ExtensionMetadataDataset;
-import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
-import org.apache.asterix.metadata.api.IMetadataEntityTupleTranslatorFactory;
-import org.apache.asterix.om.types.BuiltinType;
-import org.apache.asterix.om.types.IAType;
-
-public class GraphixMetadataIndexes {
-    public static final String METADATA_DATASET_NAME_GRAPH = "Graph";
-
-    public static final ExtensionMetadataDatasetId GRAPH_METADATA_DATASET_EXTENSION_ID = new ExtensionMetadataDatasetId(
-            GraphixMetadataExtension.GRAPHIX_METADATA_EXTENSION_ID, METADATA_DATASET_NAME_GRAPH);
-
-    public static final MetadataIndexImmutableProperties PROPERTIES_GRAPH_METADATA_DATASET =
-            new MetadataIndexImmutableProperties(METADATA_DATASET_NAME_GRAPH,
-                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID,
-                    MetadataIndexImmutableProperties.FIRST_AVAILABLE_EXTENSION_METADATA_DATASET_ID);
-
-    public static final ExtensionMetadataDataset<Graph> GRAPH_DATASET = new ExtensionMetadataDataset<>(
-            PROPERTIES_GRAPH_METADATA_DATASET, 3, new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING },
-            Arrays.asList(Collections.singletonList(FIELD_NAME_DATAVERSE_NAME),
-                    Collections.singletonList(GraphixMetadataRecordTypes.FIELD_NAME_GRAPH_NAME)),
-            0, GraphixMetadataRecordTypes.GRAPH_RECORDTYPE, true, new int[] { 0, 1 },
-            GRAPH_METADATA_DATASET_EXTENSION_ID,
-            (IMetadataEntityTupleTranslatorFactory<Graph>) GraphTupleTranslator::new);
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataRecordTypes.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataRecordTypes.java
deleted file mode 100644
index 055056d..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixMetadataRecordTypes.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.metadata.bootstrap;
-
-import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
-import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DEPENDENCIES;
-import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY;
-
-import org.apache.asterix.metadata.bootstrap.MetadataRecordTypes;
-import org.apache.asterix.om.types.AOrderedListType;
-import org.apache.asterix.om.types.ARecordType;
-import org.apache.asterix.om.types.BuiltinType;
-import org.apache.asterix.om.types.IAType;
-
-public final class GraphixMetadataRecordTypes {
-    public static final String RECORD_NAME_GRAPH = "GraphRecordType";
-    public static final String RECORD_NAME_VERTICES = "VerticesRecordType";
-    public static final String RECORD_NAME_EDGES = "EdgesRecordType";
-
-    public static final String FIELD_NAME_DEFINITIONS = "Definitions";
-    public static final String FIELD_NAME_DESTINATION_KEY = "DestinationKey";
-    public static final String FIELD_NAME_DESTINATION_LABEL = "DestinationLabel";
-    public static final String FIELD_NAME_EDGES = "Edges";
-    public static final String FIELD_NAME_GRAPH_NAME = "GraphName";
-    public static final String FIELD_NAME_LABEL = "Label";
-    public static final String FIELD_NAME_SOURCE_KEY = "SourceKey";
-    public static final String FIELD_NAME_SOURCE_LABEL = "SourceLabel";
-    public static final String FIELD_NAME_VERTICES = "Vertices";
-
-    public static final int GRAPH_VERTICES_ARECORD_LABEL_FIELD_INDEX = 0;
-    public static final int GRAPH_VERTICES_ARECORD_PRIMARY_KEY_FIELD_INDEX = 1;
-    public static final int GRAPH_VERTICES_ARECORD_DEFINITIONS_FIELD_INDEX = 2;
-    public static final ARecordType VERTICES_RECORDTYPE = MetadataRecordTypes.createRecordType(RECORD_NAME_VERTICES,
-            new String[] { FIELD_NAME_LABEL, FIELD_NAME_PRIMARY_KEY, FIELD_NAME_DEFINITIONS },
-            new IAType[] { BuiltinType.ASTRING,
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                    new AOrderedListType(BuiltinType.ASTRING, null) },
-            true);
-
-    public static final int GRAPH_EDGES_ARECORD_LABEL_FIELD_INDEX = 0;
-    public static final int GRAPH_EDGES_ARECORD_DEST_LABEL_FIELD_INDEX = 1;
-    public static final int GRAPH_EDGES_ARECORD_SOURCE_LABEL_FIELD_INDEX = 2;
-    public static final int GRAPH_EDGES_ARECORD_PRIMARY_KEY_FIELD_INDEX = 3;
-    public static final int GRAPH_EDGES_ARECORD_DEST_KEY_FIELD_INDEX = 4;
-    public static final int GRAPH_EDGES_ARECORD_SOURCE_KEY_FIELD_INDEX = 5;
-    public static final int GRAPH_EDGES_ARECORD_DEFINITIONS_FIELD_INDEX = 6;
-    public static final ARecordType EDGES_RECORDTYPE = MetadataRecordTypes.createRecordType(RECORD_NAME_EDGES,
-            new String[] { FIELD_NAME_LABEL, FIELD_NAME_DESTINATION_LABEL, FIELD_NAME_SOURCE_LABEL,
-                    FIELD_NAME_PRIMARY_KEY, FIELD_NAME_DESTINATION_KEY, FIELD_NAME_SOURCE_KEY, FIELD_NAME_DEFINITIONS },
-            new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.ASTRING,
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                    new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                    new AOrderedListType(BuiltinType.ASTRING, null) },
-            true);
-
-    public static final int GRAPH_ARECORD_DATAVERSENAME_FIELD_INDEX = 0;
-    public static final int GRAPH_ARECORD_GRAPHNAME_FIELD_INDEX = 1;
-    public static final int GRAPH_ARECORD_DEPENDENCIES_FIELD_INDEX = 2;
-    public static final int GRAPH_ARECORD_VERTICES_FIELD_INDEX = 3;
-    public static final int GRAPH_ARECORD_EDGES_FIELD_INDEX = 4;
-    public static final ARecordType GRAPH_RECORDTYPE = MetadataRecordTypes.createRecordType(RECORD_NAME_GRAPH,
-            new String[] { FIELD_NAME_DATAVERSE_NAME, FIELD_NAME_GRAPH_NAME, FIELD_NAME_DEPENDENCIES,
-                    FIELD_NAME_VERTICES, FIELD_NAME_EDGES },
-            new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING,
-                    new AOrderedListType(new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null),
-                            null),
-                    new AOrderedListType(VERTICES_RECORDTYPE, null), new AOrderedListType(EDGES_RECORDTYPE, null) },
-            true);
-}
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
new file mode 100644
index 0000000..0d99dc6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/GraphixRecordDetailProvider.java
@@ -0,0 +1,344 @@
+/*
+ * 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.metadata.bootstrap;
+
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DEPENDENCIES;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_KIND;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY;
+
+import org.apache.asterix.metadata.bootstrap.MetadataRecordTypes;
+import org.apache.asterix.om.base.ARecord;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.types.AOrderedListType;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.AUnionType;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+
+/**
+ * Provide detail about our two metadata extension datasets (Graph and GraphDependency), as well as their associated
+ * record types (vertex definitions, edge definitions, vertex records, edge records, dependency records).
+ */
+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";
+    public static final String FIELD_NAME_EDGES = "Edges";
+    public static final String FIELD_NAME_ENTITY_NAME = "EntityName";
+    public static final String FIELD_NAME_ENTITY_DETAIL = "EntityDetail";
+    public static final String FIELD_NAME_GRAPH_NAME = "GraphName";
+    public static final String FIELD_NAME_LABEL = "Label";
+    public static final String FIELD_NAME_SOURCE_KEY = "SourceKey";
+    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;
+    }
+
+    public static IRecordTypeDetail getGraphRecordDetail() {
+        return graphRecordDetail;
+    }
+
+    public static IRecordTypeDetail getDependencyRecordDetail() {
+        return dependencyRecordDetail;
+    }
+
+    public static IRecordTypeDetail getGraphDependencyRecordDetail() {
+        return graphDependencyRecordDetail;
+    }
+
+    private static abstract class AbstractRecordTypeDetail implements IRecordTypeDetail {
+        // We set this in our child.
+        protected ARecordType recordType;
+
+        @Override
+        public ARecordType getRecordType() {
+            return recordType;
+        }
+
+        @Override
+        public IAObject getObjectForField(ARecord record, String fieldName) {
+            return record.getValueByPos(getIndexForField(fieldName));
+        }
+
+        @Override
+        public IAType getTypeForField(String fieldName) {
+            return getRecordType().getFieldTypes()[getIndexForField(fieldName)];
+        }
+    }
+
+    // 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 };
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING,
+                    new AOrderedListType(vertexDefRecordDetail.getRecordType(), null) };
+            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
+        }
+
+        @Override
+        public String getRecordTypeName() {
+            return "VertexRecordType";
+        }
+
+        @Override
+        public int getIndexForField(String fieldName) {
+            switch (fieldName) {
+                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:
+                    return 1;
+
+                case FIELD_NAME_BODY:
+                    return 2;
+
+                default:
+                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
+            }
+        }
+    };
+
+    // Record type for an Edge object attached to a Graph.
+    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 };
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.ASTRING,
+                    new AOrderedListType(edgeDefRecordDetail.getRecordType(), null) };
+            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
+        }
+
+        @Override
+        public String getRecordTypeName() {
+            return "EdgeRecordType";
+        }
+
+        @Override
+        public int getIndexForField(String fieldName) {
+            switch (fieldName) {
+                case FIELD_NAME_LABEL:
+                    return 0;
+
+                case FIELD_NAME_DESTINATION_LABEL:
+                    return 1;
+
+                case FIELD_NAME_SOURCE_LABEL:
+                    return 2;
+
+                case FIELD_NAME_DEFINITIONS:
+                    return 3;
+
+                default:
+                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
+            }
+        }
+    };
+
+    // Record type for a graph.
+    private static final IRecordTypeDetail graphRecordDetail = new AbstractRecordTypeDetail() {
+        { // Construct our record type here.
+            String[] fieldNames = new String[] { FIELD_NAME_DATAVERSE_NAME, FIELD_NAME_GRAPH_NAME, FIELD_NAME_VERTICES,
+                    FIELD_NAME_EDGES };
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING,
+                    new AOrderedListType(vertexRecordDetail.getRecordType(), null),
+                    new AOrderedListType(edgeRecordDetail.getRecordType(), null) };
+            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
+        }
+
+        @Override
+        public String getRecordTypeName() {
+            return "GraphRecordType";
+        }
+
+        @Override
+        public int getIndexForField(String fieldName) {
+            switch (fieldName) {
+                case FIELD_NAME_DATAVERSE_NAME:
+                    return 0;
+
+                case FIELD_NAME_GRAPH_NAME:
+                    return 1;
+
+                case FIELD_NAME_VERTICES:
+                    return 2;
+
+                case FIELD_NAME_EDGES:
+                    return 3;
+
+                default:
+                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
+            }
+        }
+    };
+
+    // Record type for a dependency attached to a GraphDependency.
+    private static final IRecordTypeDetail dependencyRecordDetail = new AbstractRecordTypeDetail() {
+        { // Construct our record type here.
+            String[] fieldNames = new String[] { FIELD_NAME_DATAVERSE_NAME, FIELD_NAME_ENTITY_NAME, FIELD_NAME_KIND,
+                    FIELD_NAME_ENTITY_DETAIL };
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.ASTRING,
+                    AUnionType.createMissableType(BuiltinType.ASTRING) };
+            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
+        }
+
+        @Override
+        public String getRecordTypeName() {
+            return "DependencyRecordType";
+        }
+
+        @Override
+        public int getIndexForField(String fieldName) {
+            switch (fieldName) {
+                case FIELD_NAME_DATAVERSE_NAME:
+                    return 0;
+
+                case FIELD_NAME_ENTITY_NAME:
+                    return 1;
+
+                case FIELD_NAME_KIND:
+                    return 2;
+
+                case FIELD_NAME_ENTITY_DETAIL:
+                    return 3;
+
+                default:
+                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
+            }
+        }
+    };
+
+    // Record type for a GraphDependency.
+    private static final IRecordTypeDetail graphDependencyRecordDetail = new AbstractRecordTypeDetail() {
+        { // Construct our record type here.
+            String[] fieldNames = new String[] { FIELD_NAME_DEPENDENCY_ID, FIELD_NAME_DATAVERSE_NAME,
+                    FIELD_NAME_ENTITY_NAME, FIELD_NAME_KIND, FIELD_NAME_DEPENDENCIES, FIELD_NAME_ENTITY_DETAIL };
+            IAType[] fieldTypes = new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.ASTRING,
+                    BuiltinType.ASTRING, new AOrderedListType(dependencyRecordDetail.getRecordType(), null),
+                    AUnionType.createMissableType(BuiltinType.ASTRING) };
+            recordType = MetadataRecordTypes.createRecordType(getRecordTypeName(), fieldNames, fieldTypes, true);
+        }
+
+        @Override
+        public String getRecordTypeName() {
+            return "GraphDependencyRecordType";
+        }
+
+        @Override
+        public int getIndexForField(String fieldName) {
+            switch (fieldName) {
+                case FIELD_NAME_DEPENDENCY_ID:
+                    return 0;
+
+                case FIELD_NAME_DATAVERSE_NAME:
+                    return 1;
+
+                case FIELD_NAME_ENTITY_NAME:
+                    return 2;
+
+                case FIELD_NAME_KIND:
+                    return 3;
+
+                case FIELD_NAME_DEPENDENCIES:
+                    return 4;
+
+                case FIELD_NAME_ENTITY_DETAIL:
+                    return 5;
+
+                default:
+                    throw new IllegalArgumentException("Name " + fieldName + " not found for this record detail!");
+            }
+        }
+    };
+}
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IGraphixIndexDetail.java
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IGraphixIndexDetail.java
index 9667fd6..f418f77 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IGraphixIndexDetail.java
@@ -16,7 +16,15 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.metadata.bootstrap;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+import org.apache.asterix.metadata.api.ExtensionMetadataDataset;
+import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
+
+public interface IGraphixIndexDetail<T> {
+    String getDatasetName();
+
+    ExtensionMetadataDatasetId getExtensionDatasetID();
+
+    ExtensionMetadataDataset<T> getExtensionDataset();
+}
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IRecordTypeDetail.java
similarity index 61%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IRecordTypeDetail.java
index 9667fd6..1a5fc01 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/bootstrap/IRecordTypeDetail.java
@@ -16,7 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.asterix.graphix.metadata.bootstrap;
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+import org.apache.asterix.om.base.ARecord;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.IAType;
+
+public interface IRecordTypeDetail {
+    String getRecordTypeName();
+
+    ARecordType getRecordType();
+
+    IAObject getObjectForField(ARecord record, String fieldName);
+
+    IAType getTypeForField(String fieldName);
+
+    int getIndexForField(String fieldName);
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/Graph.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/Graph.java
deleted file mode 100644
index 21afc2d..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/Graph.java
+++ /dev/null
@@ -1,348 +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.metadata.entities;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
-import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
-import org.apache.asterix.graphix.metadata.bootstrap.GraphixMetadataIndexes;
-import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
-import org.apache.asterix.metadata.api.IExtensionMetadataEntity;
-
-/**
- * Metadata describing a graph view, composed of vertices and edges.
- */
-public class Graph implements IExtensionMetadataEntity {
-    private static final long serialVersionUID = 1L;
-
-    private final GraphIdentifier identifier;
-    private final GraphDependencies dependencies;
-    private final Schema graphSchema;
-
-    public Graph(GraphIdentifier identifier, Schema graphSchema, GraphDependencies dependencies) {
-        this.identifier = Objects.requireNonNull(identifier);
-        this.dependencies = dependencies;
-        this.graphSchema = graphSchema;
-    }
-
-    public DataverseName getDataverseName() {
-        return identifier.getDataverseName();
-    }
-
-    public String getGraphName() {
-        return identifier.getGraphName();
-    }
-
-    public GraphIdentifier getIdentifier() {
-        return identifier;
-    }
-
-    public Schema getGraphSchema() {
-        return graphSchema;
-    }
-
-    public GraphDependencies getDependencies() {
-        return dependencies;
-    }
-
-    @Override
-    public ExtensionMetadataDatasetId getDatasetId() {
-        return GraphixMetadataIndexes.GRAPH_METADATA_DATASET_EXTENSION_ID;
-    }
-
-    public static class Schema implements Serializable {
-        private static final long serialVersionUID = 1L;
-
-        // The element map is composed of the vertices and edges.
-        private final Map<GraphElementIdentifier, Element> elementMap = new HashMap<>();
-        private final List<Vertex> vertexList = new ArrayList<>();
-        private final List<Edge> edgeList = new ArrayList<>();
-
-        public List<Vertex> getVertices() {
-            return vertexList;
-        }
-
-        public List<Edge> getEdges() {
-            return edgeList;
-        }
-
-        public Element getElement(GraphElementIdentifier identifier) {
-            return elementMap.get(identifier);
-        }
-
-        private Schema() {
-        }
-
-        public static class Builder {
-            private final Map<String, Vertex> vertexLabelMap = new HashMap<>();
-            private final Map<String, Edge> edgeLabelMap = new HashMap<>();
-
-            // We aim to populate the schema object below.
-            private final Schema workingSchema = new Schema();
-            private final GraphIdentifier graphIdentifier;
-            private Error lastError = Error.NO_ERROR;
-
-            public Builder(GraphIdentifier graphIdentifier) {
-                this.graphIdentifier = graphIdentifier;
-            }
-
-            /**
-             * @return Null if the primary keys of an existing vertex conflict with the vertex to-be-added. The vertex
-             * to-be-added otherwise.
-             */
-            public Vertex addVertex(String labelName, List<List<String>> primaryKeyFieldNames, String definition) {
-                if (!vertexLabelMap.containsKey(labelName)) {
-                    GraphElementIdentifier identifier =
-                            new GraphElementIdentifier(graphIdentifier, GraphElementIdentifier.Kind.VERTEX, labelName);
-                    Vertex newVertex = new Vertex(identifier, primaryKeyFieldNames, definition);
-                    workingSchema.vertexList.add(newVertex);
-                    vertexLabelMap.put(labelName, newVertex);
-                    return newVertex;
-
-                } else {
-                    Vertex existingVertex = vertexLabelMap.get(labelName);
-                    if (!existingVertex.getPrimaryKeyFieldNames().equals(primaryKeyFieldNames)) {
-                        lastError = Error.CONFLICTING_PRIMARY_KEY;
-                        return null;
-                    }
-                    existingVertex.getDefinitions().add(definition);
-                    return existingVertex;
-                }
-            }
-
-            /**
-             * @return Null if there exists no vertex with the given source label or destination label, OR if the
-             * primary key / source vertex / destination vertex of an existing edge conflict with the edge to-be-added.
-             */
-            public Edge addEdge(String edgeLabelName, String destinationLabelName, String sourceLabelName,
-                    List<List<String>> destinationKeyFieldNames) {
-                if (!vertexLabelMap.containsKey(sourceLabelName)) {
-                    lastError = Error.SOURCE_VERTEX_NOT_FOUND;
-                    return null;
-                }
-
-                Vertex representativeSourceVertex = vertexLabelMap.get(sourceLabelName);
-                return addEdge(edgeLabelName, destinationLabelName, sourceLabelName,
-                        representativeSourceVertex.getPrimaryKeyFieldNames(), destinationKeyFieldNames,
-                        representativeSourceVertex.getPrimaryKeyFieldNames(), "");
-            }
-
-            /**
-             * @return Null if there exists no vertex with the given source label or destination label, OR if the
-             * primary key / source vertex / destination vertex of an existing edge conflict with the edge to-be-added.
-             */
-            public Edge addEdge(String edgeLabelName, String destinationLabelName, String sourceLabelName,
-                    List<List<String>> primaryKeyFieldNames, List<List<String>> destinationKeyFieldNames,
-                    List<List<String>> sourceKeyFieldNames, String definition) {
-                if (!vertexLabelMap.containsKey(sourceLabelName)) {
-                    lastError = Error.SOURCE_VERTEX_NOT_FOUND;
-                    return null;
-
-                } else if (!vertexLabelMap.containsKey(destinationLabelName)) {
-                    lastError = Error.DESTINATION_VERTEX_NOT_FOUND;
-                    return null;
-
-                } else if (edgeLabelMap.containsKey(edgeLabelName)) {
-                    Edge existingEdge = edgeLabelMap.get(edgeLabelName);
-                    if (!existingEdge.getPrimaryKeyFieldNames().equals(primaryKeyFieldNames)) {
-                        lastError = Error.CONFLICTING_PRIMARY_KEY;
-                        return null;
-
-                    } else if (!existingEdge.getSourceLabelName().equals(sourceLabelName)) {
-                        // This also covers any source-key conflicts.
-                        lastError = Error.CONFLICTING_SOURCE_VERTEX;
-                        return null;
-
-                    } else if (!existingEdge.getDestinationLabelName().equals(destinationLabelName)) {
-                        // This also covers any destination-key conflicts.
-                        lastError = Error.CONFLICTING_DESTINATION_VERTEX;
-                        return null;
-                    }
-                    existingEdge.getDefinitions().add(definition);
-                    return existingEdge;
-
-                } else {
-                    GraphElementIdentifier identifier = new GraphElementIdentifier(graphIdentifier,
-                            GraphElementIdentifier.Kind.EDGE, edgeLabelName);
-                    Edge newEdge = new Edge(identifier, primaryKeyFieldNames, destinationKeyFieldNames,
-                            sourceKeyFieldNames, vertexLabelMap.get(destinationLabelName),
-                            vertexLabelMap.get(sourceLabelName), definition);
-                    workingSchema.edgeList.add(newEdge);
-                    edgeLabelMap.put(edgeLabelName, newEdge);
-                    return newEdge;
-                }
-            }
-
-            public Schema build() {
-                // Build the element map, composed of our vertices and edges.
-                workingSchema.elementMap.clear();
-                workingSchema.getVertices().forEach(v -> workingSchema.elementMap.put(v.identifier, v));
-                workingSchema.getEdges().forEach(e -> workingSchema.elementMap.put(e.identifier, e));
-                return workingSchema;
-            }
-
-            public Error getLastError() {
-                return lastError;
-            }
-
-            public enum Error {
-                NO_ERROR,
-
-                CONFLICTING_PRIMARY_KEY,
-                CONFLICTING_SOURCE_VERTEX,
-                CONFLICTING_DESTINATION_VERTEX,
-
-                SOURCE_VERTEX_NOT_FOUND,
-                DESTINATION_VERTEX_NOT_FOUND
-            }
-        }
-    }
-
-    public static final class Vertex implements Element {
-        private static final long serialVersionUID = 1L;
-
-        private final GraphElementIdentifier identifier;
-        private final List<List<String>> primaryKeyFieldNames;
-        private final List<String> definitions;
-
-        private Vertex(GraphElementIdentifier identifier, List<List<String>> primaryKeyFieldNames, String definition) {
-            this.identifier = Objects.requireNonNull(identifier);
-            this.primaryKeyFieldNames = Objects.requireNonNull(primaryKeyFieldNames);
-            this.definitions = new ArrayList<>();
-            this.definitions.add(Objects.requireNonNull(definition));
-        }
-
-        public List<List<String>> getPrimaryKeyFieldNames() {
-            return primaryKeyFieldNames;
-        }
-
-        @Override
-        public GraphElementIdentifier getIdentifier() {
-            return identifier;
-        }
-
-        @Override
-        public String getLabelName() {
-            return identifier.getLabelName();
-        }
-
-        @Override
-        public List<String> getDefinitions() {
-            return definitions;
-        }
-
-        @Override
-        public String toString() {
-            return "(:" + getLabelName() + ") AS " + String.join(",\n", definitions);
-        }
-    }
-
-    public static final class Edge implements Element {
-        private static final long serialVersionUID = 1L;
-
-        private final List<List<String>> primaryKeyFieldNames;
-        private final List<List<String>> destinationKeyFieldNames;
-        private final List<List<String>> sourceKeyFieldNames;
-
-        private final GraphElementIdentifier identifier;
-        private final Vertex destinationVertex;
-        private final Vertex sourceVertex;
-        private final List<String> definitions;
-
-        private Edge(GraphElementIdentifier identifier, List<List<String>> primaryKeyFieldNames,
-                List<List<String>> destinationKeyFieldNames, List<List<String>> sourceKeyFieldNames,
-                Vertex destinationVertex, Vertex sourceVertex, String edgeDefinition) {
-            this.primaryKeyFieldNames = Objects.requireNonNull(primaryKeyFieldNames);
-            this.destinationKeyFieldNames = Objects.requireNonNull(destinationKeyFieldNames);
-            this.sourceKeyFieldNames = Objects.requireNonNull(sourceKeyFieldNames);
-            this.destinationVertex = Objects.requireNonNull(destinationVertex);
-            this.sourceVertex = Objects.requireNonNull(sourceVertex);
-            this.identifier = Objects.requireNonNull(identifier);
-            this.definitions = new ArrayList<>();
-            this.definitions.add(Objects.requireNonNull(edgeDefinition));
-        }
-
-        public String getDestinationLabelName() {
-            return destinationVertex.getLabelName();
-        }
-
-        public String getSourceLabelName() {
-            return sourceVertex.getLabelName();
-        }
-
-        public List<List<String>> getPrimaryKeyFieldNames() {
-            return primaryKeyFieldNames;
-        }
-
-        public List<List<String>> getDestinationKeyFieldNames() {
-            return destinationKeyFieldNames;
-        }
-
-        public List<List<String>> getSourceKeyFieldNames() {
-            return sourceKeyFieldNames;
-        }
-
-        public Vertex getDestinationVertex() {
-            return destinationVertex;
-        }
-
-        public Vertex getSourceVertex() {
-            return sourceVertex;
-        }
-
-        @Override
-        public GraphElementIdentifier getIdentifier() {
-            return identifier;
-        }
-
-        @Override
-        public String getLabelName() {
-            return identifier.getLabelName();
-        }
-
-        @Override
-        public List<String> getDefinitions() {
-            return definitions;
-        }
-
-        @Override
-        public String toString() {
-            String edgeBodyPattern = "[:" + getLabelName() + "]";
-            String sourceNodePattern = "(:" + getSourceLabelName() + ")";
-            String destinationNodePattern = "(:" + getDestinationLabelName() + ")";
-            String edgePattern = sourceNodePattern + "-" + edgeBodyPattern + "->" + destinationNodePattern;
-            return edgePattern + " AS " + String.join(",\n", getDefinitions());
-        }
-    }
-
-    public interface Element extends Serializable {
-        GraphElementIdentifier getIdentifier();
-
-        String getLabelName();
-
-        List<String> getDefinitions();
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/GraphDependencies.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/GraphDependencies.java
deleted file mode 100644
index 0e7aa5a..0000000
--- a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entities/GraphDependencies.java
+++ /dev/null
@@ -1,76 +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.metadata.entities;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.asterix.common.exceptions.CompilationException;
-import org.apache.asterix.common.metadata.DataverseName;
-import org.apache.asterix.lang.common.base.Expression;
-import org.apache.asterix.lang.common.base.IQueryRewriter;
-import org.apache.asterix.lang.common.util.ExpressionUtils;
-import org.apache.asterix.metadata.entities.DependencyKind;
-import org.apache.hyracks.algebricks.common.utils.Pair;
-import org.apache.hyracks.algebricks.common.utils.Triple;
-
-/**
- * Helper class to manage dependencies for a graph metadata entity.
- */
-public class GraphDependencies {
-    private final List<Triple<DataverseName, String, String>> datasetDependencies;
-    private final List<Triple<DataverseName, String, String>> synonymDependencies;
-    private final List<Triple<DataverseName, String, String>> functionDependencies;
-
-    public GraphDependencies(List<List<Triple<DataverseName, String, String>>> listRepresentation) {
-        datasetDependencies = listRepresentation.get(0);
-        synonymDependencies = listRepresentation.get(1);
-        functionDependencies = listRepresentation.get(2);
-    }
-
-    public GraphDependencies() {
-        datasetDependencies = new ArrayList<>();
-        synonymDependencies = new ArrayList<>();
-        functionDependencies = new ArrayList<>();
-    }
-
-    public List<List<Triple<DataverseName, String, String>>> getListRepresentation() {
-        return List.of(datasetDependencies, synonymDependencies, functionDependencies);
-    }
-
-    public Iterator<Pair<DependencyKind, Triple<DataverseName, String, String>>> getIterator() {
-        List<Pair<DependencyKind, Triple<DataverseName, String, String>>> resultant = new ArrayList<>();
-        for (Triple<DataverseName, String, String> datasetDependency : datasetDependencies) {
-            resultant.add(new Pair<>(DependencyKind.DATASET, datasetDependency));
-        }
-        for (Triple<DataverseName, String, String> synonymDependency : synonymDependencies) {
-            resultant.add(new Pair<>(DependencyKind.SYNONYM, synonymDependency));
-        }
-        for (Triple<DataverseName, String, String> functionDependency : functionDependencies) {
-            resultant.add(new Pair<>(DependencyKind.FUNCTION, functionDependency));
-        }
-        return resultant.listIterator();
-    }
-
-    public void collectDependencies(Expression expression, IQueryRewriter queryRewriter) throws CompilationException {
-        ExpressionUtils.collectDependencies(expression, queryRewriter, datasetDependencies, synonymDependencies,
-                functionDependencies);
-    }
-}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/DependencyIdentifier.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/DependencyIdentifier.java
new file mode 100644
index 0000000..7c63de8
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/DependencyIdentifier.java
@@ -0,0 +1,112 @@
+/*
+ * 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.metadata.entity.dependency;
+
+import java.util.Objects;
+
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.metadata.utils.DatasetUtil;
+import org.apache.asterix.metadata.utils.MetadataUtil;
+
+/**
+ * An identifier for a dependency related to a {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} instance.
+ * A graph may depend on datasets, synonyms, functions, and other graphs. Similarly, functions and views may depend
+ * on graphs themselves.
+ */
+public class DependencyIdentifier {
+    private final DataverseName dataverseName;
+    private final String entityName;
+    private final String entityDetail;
+    private final Kind dependencyKind;
+
+    public DependencyIdentifier(DataverseName dataverseName, String entityName, Kind dependencyKind) {
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.entityName = Objects.requireNonNull(entityName);
+        this.entityDetail = null;
+        this.dependencyKind = Objects.requireNonNull(dependencyKind);
+    }
+
+    public DependencyIdentifier(DataverseName dataverseName, String entityName, String entityDetail,
+            Kind dependencyKind) {
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.entityName = Objects.requireNonNull(entityName);
+        this.entityDetail = Objects.requireNonNull(entityDetail);
+        this.dependencyKind = Objects.requireNonNull(dependencyKind);
+    }
+
+    public DataverseName getDataverseName() {
+        return dataverseName;
+    }
+
+    public String getEntityName() {
+        return entityName;
+    }
+
+    public String getEntityDetail() {
+        return entityDetail;
+    }
+
+    public Kind getDependencyKind() {
+        return dependencyKind;
+    }
+
+    public String getDisplayName() {
+        switch (dependencyKind) {
+            case DATASET:
+                return DatasetUtil.getFullyQualifiedDisplayName(dataverseName, entityName);
+
+            case SYNONYM:
+            case GRAPH:
+                return MetadataUtil.getFullyQualifiedDisplayName(dataverseName, entityName);
+
+            case FUNCTION:
+                return new FunctionSignature(dataverseName, entityName,
+                        Integer.parseInt(Objects.requireNonNull(entityDetail))).toString();
+        }
+        return null;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (this == object) {
+            return true;
+        }
+        if (!(object instanceof DependencyIdentifier)) {
+            return false;
+        }
+        DependencyIdentifier that = (DependencyIdentifier) object;
+        return that.getDataverseName().equals(this.getDataverseName())
+                && that.getDependencyKind().equals(this.getDependencyKind())
+                && that.getEntityName().equals(this.getEntityName())
+                && Objects.equals(that.getEntityDetail(), this.getEntityDetail());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(dataverseName, dependencyKind, entityName, entityDetail);
+    }
+
+    public enum Kind {
+        GRAPH,
+        DATASET,
+        SYNONYM,
+        FUNCTION
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.java
new file mode 100644
index 0000000..5e73325
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/FunctionRequirements.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.metadata.entity.dependency;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.om.base.AGeneratedUUID;
+import org.apache.asterix.om.base.AUUID;
+
+/**
+ * A collection of {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} dependencies associated with a
+ * {@link org.apache.asterix.metadata.entities.Function} instance. This does **not** include non-graph dependencies
+ * for functions.
+ */
+public class FunctionRequirements implements IEntityRequirements {
+    private final Set<DependencyIdentifier> functionRequirements;
+    private final FunctionSignature functionSignature;
+
+    // Physically, our requirements are indexed by a UUID. Logically, we ignore this.
+    private final AUUID primaryKeyValue;
+
+    public FunctionRequirements(FunctionSignature functionSignature, Set<DependencyIdentifier> functionRequirements) {
+        this.functionRequirements = Objects.requireNonNull(functionRequirements);
+        this.functionSignature = Objects.requireNonNull(functionSignature);
+        this.primaryKeyValue = new AGeneratedUUID();
+    }
+
+    public FunctionRequirements(FunctionSignature functionSignature, Set<DependencyIdentifier> functionRequirements,
+            AUUID primaryKeyValue) {
+        this.functionRequirements = Objects.requireNonNull(functionRequirements);
+        this.functionSignature = Objects.requireNonNull(functionSignature);
+        this.primaryKeyValue = Objects.requireNonNull(primaryKeyValue);
+    }
+
+    public String getArityAsString() {
+        return String.valueOf(functionSignature.getArity());
+    }
+
+    @Override
+    public AUUID getPrimaryKeyValue() {
+        return primaryKeyValue;
+    }
+
+    @Override
+    public DataverseName getDataverseName() {
+        return functionSignature.getDataverseName();
+    }
+
+    @Override
+    public String getEntityName() {
+        // Note: this entity name is not unique! Use this in conjunction with the arity.
+        return functionSignature.getName();
+    }
+
+    @Override
+    public String getDisplayName() {
+        return functionSignature.toString(true);
+    }
+
+    @Override
+    public DependentKind getDependentKind() {
+        return DependentKind.FUNCTION;
+    }
+
+    @Override
+    public Iterator<DependencyIdentifier> iterator() {
+        return functionRequirements.iterator();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/GraphRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/GraphRequirements.java
new file mode 100644
index 0000000..98c669a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/GraphRequirements.java
@@ -0,0 +1,124 @@
+/*
+ * 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.metadata.entity.dependency;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.lang.common.base.Expression;
+import org.apache.asterix.lang.common.base.IQueryRewriter;
+import org.apache.asterix.lang.common.util.ExpressionUtils;
+import org.apache.asterix.metadata.utils.MetadataUtil;
+import org.apache.asterix.om.base.AGeneratedUUID;
+import org.apache.asterix.om.base.AUUID;
+import org.apache.hyracks.algebricks.common.utils.Triple;
+
+/**
+ * A collection of dependencies (datasets, synonyms, functions, and graphs) associated with a
+ * {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} instance.
+ */
+public class GraphRequirements implements IEntityRequirements {
+    // A graph potentially depends on datasets, synonyms, functions, and graphs.
+    private final Set<DependencyIdentifier> graphRequirements;
+    private final DataverseName dataverseName;
+    private final String graphName;
+
+    // Physically, our requirements are indexed by a UUID. Logically, we ignore this.
+    private AUUID primaryKeyValue;
+
+    public GraphRequirements(DataverseName dataverseName, String graphName, Set<DependencyIdentifier> graphRequirements,
+            AUUID primaryKeyValue) {
+        this.graphRequirements = Objects.requireNonNull(graphRequirements);
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.graphName = Objects.requireNonNull(graphName);
+        this.primaryKeyValue = Objects.requireNonNull(primaryKeyValue);
+    }
+
+    public GraphRequirements(DataverseName dataverseName, String graphName) {
+        this.graphRequirements = new HashSet<>();
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.graphName = Objects.requireNonNull(graphName);
+        this.primaryKeyValue = new AGeneratedUUID();
+    }
+
+    public void loadNonGraphDependencies(Expression body, IQueryRewriter queryRewriter) throws CompilationException {
+        // Collect our dependencies as triples.
+        List<Triple<DataverseName, String, String>> datasetDependencies = new ArrayList<>();
+        List<Triple<DataverseName, String, String>> synonymDependencies = new ArrayList<>();
+        List<Triple<DataverseName, String, String>> functionDependencies = new ArrayList<>();
+        ExpressionUtils.collectDependencies(body, queryRewriter, datasetDependencies, synonymDependencies,
+                functionDependencies);
+
+        // Transform and load into our requirements list.
+        datasetDependencies.stream()
+                .map(t -> new DependencyIdentifier(t.first, t.second, DependencyIdentifier.Kind.DATASET))
+                .forEach(graphRequirements::add);
+        synonymDependencies.stream()
+                .map(t -> new DependencyIdentifier(t.first, t.second, DependencyIdentifier.Kind.SYNONYM))
+                .forEach(graphRequirements::add);
+        functionDependencies.stream()
+                .map(t -> new DependencyIdentifier(t.first, t.second, t.third, DependencyIdentifier.Kind.FUNCTION))
+                .forEach(graphRequirements::add);
+    }
+
+    public void loadGraphDependencies(Collection<DependencyIdentifier> graphDependencies) {
+        graphRequirements.addAll(graphDependencies);
+    }
+
+    public void setPrimaryKeyValue(AUUID primaryKeyValue) {
+        this.primaryKeyValue = primaryKeyValue;
+    }
+
+    @Override
+    public AUUID getPrimaryKeyValue() {
+        return primaryKeyValue;
+    }
+
+    @Override
+    public DataverseName getDataverseName() {
+        return dataverseName;
+    }
+
+    @Override
+    public String getEntityName() {
+        return graphName;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return MetadataUtil.getFullyQualifiedDisplayName(dataverseName, graphName);
+    }
+
+    @Override
+    public DependentKind getDependentKind() {
+        return DependentKind.GRAPH;
+    }
+
+    @Override
+    public Iterator<DependencyIdentifier> iterator() {
+        return graphRequirements.iterator();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/IEntityRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/IEntityRequirements.java
new file mode 100644
index 0000000..371d4e3
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/IEntityRequirements.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.metadata.entity.dependency;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixIndexDetailProvider;
+import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
+import org.apache.asterix.metadata.api.IExtensionMetadataEntity;
+import org.apache.asterix.om.base.AUUID;
+
+/**
+ * Metadata for describing the pair [entity, list of dependencies for said entity]. This includes the following:
+ * 1. A primary key value associated with the pair (logically, this is ignored).
+ * 2. The dataverse name associated with the entity.
+ * 3. The name associated with the entity.
+ * 4. The kind associated with the entity (FUNCTION, GRAPH, or VIEW).
+ * 5. An iterator of the dependencies associated with the entity.
+ */
+public interface IEntityRequirements extends Iterable<DependencyIdentifier>, IExtensionMetadataEntity {
+    AUUID getPrimaryKeyValue();
+
+    DataverseName getDataverseName();
+
+    String getEntityName();
+
+    String getDisplayName();
+
+    DependentKind getDependentKind();
+
+    enum DependentKind {
+        FUNCTION,
+        GRAPH,
+        VIEW,
+    }
+
+    default ExtensionMetadataDatasetId getDatasetId() {
+        return GraphixIndexDetailProvider.getGraphDependencyIndexDetail().getExtensionDatasetID();
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java
new file mode 100644
index 0000000..2e529a6
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/dependency/ViewRequirements.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.metadata.entity.dependency;
+
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.metadata.utils.DatasetUtil;
+import org.apache.asterix.om.base.AGeneratedUUID;
+import org.apache.asterix.om.base.AUUID;
+
+/**
+ * A collection of {@link org.apache.asterix.graphix.metadata.entity.schema.Graph} dependencies associated with a view
+ * instance. This does **not** include non-graph dependencies for views.
+ */
+public class ViewRequirements implements IEntityRequirements {
+    private final Set<DependencyIdentifier> viewRequirements;
+    private final DataverseName dataverseName;
+    private final String viewName;
+
+    // Physically, our requirements are indexed by a UUID. Logically, we ignore this.
+    private final AUUID primaryKeyValue;
+
+    public ViewRequirements(DataverseName dataverseName, String viewName, Set<DependencyIdentifier> viewRequirements) {
+        this.viewRequirements = Objects.requireNonNull(viewRequirements);
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.viewName = Objects.requireNonNull(viewName);
+        this.primaryKeyValue = new AGeneratedUUID();
+    }
+
+    public ViewRequirements(DataverseName dataverseName, String viewName, Set<DependencyIdentifier> viewRequirements,
+            AUUID primaryKeyValue) {
+        this.viewRequirements = Objects.requireNonNull(viewRequirements);
+        this.dataverseName = Objects.requireNonNull(dataverseName);
+        this.viewName = Objects.requireNonNull(viewName);
+        this.primaryKeyValue = Objects.requireNonNull(primaryKeyValue);
+    }
+
+    @Override
+    public AUUID getPrimaryKeyValue() {
+        return primaryKeyValue;
+    }
+
+    @Override
+    public DataverseName getDataverseName() {
+        return dataverseName;
+    }
+
+    @Override
+    public String getEntityName() {
+        return viewName;
+    }
+
+    @Override
+    public String getDisplayName() {
+        return DatasetUtil.getFullyQualifiedDisplayName(dataverseName, viewName);
+    }
+
+    @Override
+    public DependentKind getDependentKind() {
+        return DependentKind.VIEW;
+    }
+
+    @Override
+    public Iterator<DependencyIdentifier> iterator() {
+        return viewRequirements.iterator();
+    }
+}
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
new file mode 100644
index 0000000..2cd9036
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Edge.java
@@ -0,0 +1,123 @@
+/*
+ * 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.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;
+
+/**
+ * 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.
+ */
+public class Edge implements Element {
+    private static final long serialVersionUID = 1L;
+
+    private final GraphElementIdentifier identifier;
+    private final Vertex destinationVertex;
+    private final Vertex sourceVertex;
+    private final List<Definition> definitions;
+
+    /**
+     * 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);
+        this.identifier = Objects.requireNonNull(identifier);
+        this.definitions = new ArrayList<>();
+        this.definitions.add(Objects.requireNonNull(edgeDefinition));
+    }
+
+    public static class Definition {
+        private final List<List<String>> destinationKeyFieldNames;
+        private final List<List<String>> sourceKeyFieldNames;
+        private final String definition;
+
+        Definition(List<List<String>> destinationKeyFieldNames, List<List<String>> sourceKeyFieldNames,
+                String definition) {
+            this.destinationKeyFieldNames = Objects.requireNonNull(destinationKeyFieldNames);
+            this.sourceKeyFieldNames = Objects.requireNonNull(sourceKeyFieldNames);
+            this.definition = Objects.requireNonNull(definition);
+        }
+
+        public List<List<String>> getDestinationKeyFieldNames() {
+            return destinationKeyFieldNames;
+        }
+
+        public List<List<String>> getSourceKeyFieldNames() {
+            return sourceKeyFieldNames;
+        }
+
+        public String getDefinition() {
+            return definition;
+        }
+    }
+
+    public ElementLabel getDestinationLabel() {
+        return destinationVertex.getLabel();
+    }
+
+    public ElementLabel getSourceLabel() {
+        return sourceVertex.getLabel();
+    }
+
+    public Vertex getDestinationVertex() {
+        return destinationVertex;
+    }
+
+    public Vertex getSourceVertex() {
+        return sourceVertex;
+    }
+
+    public List<Definition> getDefinitions() {
+        return definitions;
+    }
+
+    @Override
+    public GraphElementIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public ElementLabel getLabel() {
+        return identifier.getElementLabel();
+    }
+
+    @Override
+    public List<String> getDefinitionBodies() {
+        return definitions.stream().map(Definition::getDefinition).collect(Collectors.toList());
+    }
+
+    @Override
+    public String toString() {
+        String edgeBodyPattern = "[:" + getLabel() + "]";
+        String sourceNodePattern = "(:" + getSourceLabel() + ")";
+        String destinationNodePattern = "(:" + getDestinationLabel() + ")";
+        String edgePattern = sourceNodePattern + "-" + edgeBodyPattern + "->" + destinationNodePattern;
+        return edgePattern + " AS " + String.join(",\n", getDefinitionBodies());
+    }
+}
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/Element.java
new file mode 100644
index 0000000..e0123bc
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Element.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.graphix.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;
+
+/**
+ * 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.
+ */
+public interface Element extends Serializable {
+    GraphElementIdentifier getIdentifier();
+
+    ElementLabel getLabel();
+
+    List<String> getDefinitionBodies();
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Graph.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Graph.java
new file mode 100644
index 0000000..e3b9f6f
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Graph.java
@@ -0,0 +1,63 @@
+/*
+ * 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.metadata.entity.schema;
+
+import java.util.Objects;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixIndexDetailProvider;
+import org.apache.asterix.metadata.api.ExtensionMetadataDatasetId;
+import org.apache.asterix.metadata.api.IExtensionMetadataEntity;
+
+/**
+ * Metadata describing a graph view, composed of a {@link Schema} instance and a {@link GraphIdentifier}.
+ */
+public class Graph implements IExtensionMetadataEntity {
+    private static final long serialVersionUID = 1L;
+
+    private final GraphIdentifier graphIdentifier;
+    private final Schema graphSchema;
+
+    public Graph(GraphIdentifier graphIdentifier, Schema graphSchema) {
+        this.graphSchema = Objects.requireNonNull(graphSchema);
+        this.graphIdentifier = graphIdentifier;
+    }
+
+    public DataverseName getDataverseName() {
+        return graphIdentifier.getDataverseName();
+    }
+
+    public String getGraphName() {
+        return graphIdentifier.getGraphName();
+    }
+
+    public GraphIdentifier getIdentifier() {
+        return graphIdentifier;
+    }
+
+    public Schema getGraphSchema() {
+        return graphSchema;
+    }
+
+    @Override
+    public ExtensionMetadataDatasetId getDatasetId() {
+        return GraphixIndexDetailProvider.getGraphIndexDetail().getExtensionDatasetID();
+    }
+}
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
new file mode 100644
index 0000000..b4b965a
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Schema.java
@@ -0,0 +1,170 @@
+/*
+ * 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.metadata.entity.schema;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+import org.apache.asterix.graphix.common.metadata.GraphIdentifier;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+
+/**
+ * Metadata representation of a graph schema. A graph schema consists of:
+ * 1. A list of {@link Vertex} instances.
+ * 2. A list of {@link Edge} instances, which link the aforementioned vertices.
+ */
+public class Schema implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    // The element map is composed of the vertices and edges.
+    private final List<Vertex> vertexList = new ArrayList<>();
+    private final List<Edge> edgeList = new ArrayList<>();
+
+    public List<Vertex> getVertices() {
+        return vertexList;
+    }
+
+    public List<Edge> getEdges() {
+        return edgeList;
+    }
+
+    /**
+     * Use the {@link Builder} class to create Schema instances.
+     */
+    private Schema() {
+    }
+
+    public static class Builder {
+        private final Map<ElementLabel, Vertex> vertexLabelMap = new HashMap<>();
+        private final Map<ElementLabel, List<Edge>> edgeLabelMap = new HashMap<>();
+
+        // We aim to populate the schema object below.
+        private final Schema workingSchema;
+        private final GraphIdentifier graphIdentifier;
+        private Error lastError = Error.NO_ERROR;
+
+        public Builder(GraphIdentifier graphIdentifier) {
+            this.graphIdentifier = graphIdentifier;
+            this.workingSchema = new Schema();
+        }
+
+        /**
+         * @return Null if the primary keys of an existing vertex conflict with the vertex to-be-added. 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));
+                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;
+            }
+        }
+
+        /**
+         * @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.
+         */
+        public Edge addEdge(ElementLabel edgeLabel, ElementLabel destinationLabel, ElementLabel sourceLabel,
+                List<List<String>> destinationKeyFieldNames, List<List<String>> sourceKeyFieldNames,
+                String definitionBody) {
+            if (!vertexLabelMap.containsKey(sourceLabel)) {
+                lastError = Error.SOURCE_VERTEX_NOT_FOUND;
+                return null;
+
+            } 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;
+                        }
+                    }
+                }
+            }
+
+            // 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));
+            workingSchema.edgeList.add(newEdge);
+
+            // Update our edge label map.
+            ArrayList<Edge> edgeList = new ArrayList<>();
+            edgeList.add(newEdge);
+            edgeLabelMap.put(edgeLabel, edgeList);
+            return newEdge;
+        }
+
+        public Schema build() {
+            return workingSchema;
+        }
+
+        public Error getLastError() {
+            return lastError;
+        }
+
+        public enum Error {
+            NO_ERROR,
+
+            CONFLICTING_PRIMARY_KEY,
+            CONFLICTING_SOURCE_KEY,
+            CONFLICTING_DESTINATION_KEY,
+
+            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
new file mode 100644
index 0000000..e6950e3
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entity/schema/Vertex.java
@@ -0,0 +1,100 @@
+/*
+ * 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.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;
+
+/**
+ * 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.
+ */
+public class Vertex implements Element {
+    private static final long serialVersionUID = 1L;
+
+    private final GraphElementIdentifier identifier;
+    private final List<Definition> definitions;
+
+    /**
+     * Use {@link Schema.Builder} to build Vertex instances instead of this constructor.
+     */
+    Vertex(GraphElementIdentifier identifier, Definition definition) {
+        this.identifier = Objects.requireNonNull(identifier);
+        this.definitions = new ArrayList<>();
+        this.definitions.add(Objects.requireNonNull(definition));
+    }
+
+    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;
+    }
+
+    @Override
+    public GraphElementIdentifier getIdentifier() {
+        return identifier;
+    }
+
+    @Override
+    public ElementLabel getLabel() {
+        return identifier.getElementLabel();
+    }
+
+    @Override
+    public List<String> getDefinitionBodies() {
+        return definitions.stream().map(Definition::getDefinition).collect(Collectors.toList());
+    }
+
+    @Override
+    public String toString() {
+        return "(:" + getLabel() + ") AS " + String.join(",\n", getDefinitionBodies());
+    }
+}
diff --git a/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/DependencyTupleTranslator.java b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/DependencyTupleTranslator.java
new file mode 100644
index 0000000..77c1637
--- /dev/null
+++ b/asterix-graphix/src/main/java/org/apache/asterix/graphix/metadata/entitytupletranslators/DependencyTupleTranslator.java
@@ -0,0 +1,293 @@
+/*
+ * 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.metadata.entitytupletranslators;
+
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_DEPENDENCY_ID;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_ENTITY_DETAIL;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_ENTITY_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DEPENDENCIES;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_KIND;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.asterix.builders.IARecordBuilder;
+import org.apache.asterix.builders.OrderedListBuilder;
+import org.apache.asterix.builders.RecordBuilder;
+import org.apache.asterix.common.functions.FunctionSignature;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixIndexDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.IRecordTypeDetail;
+import org.apache.asterix.graphix.metadata.entity.dependency.DependencyIdentifier;
+import org.apache.asterix.graphix.metadata.entity.dependency.FunctionRequirements;
+import org.apache.asterix.graphix.metadata.entity.dependency.GraphRequirements;
+import org.apache.asterix.graphix.metadata.entity.dependency.IEntityRequirements;
+import org.apache.asterix.graphix.metadata.entity.dependency.ViewRequirements;
+import org.apache.asterix.metadata.entitytupletranslators.AbstractTupleTranslator;
+import org.apache.asterix.om.base.AMutableUUID;
+import org.apache.asterix.om.base.AOrderedList;
+import org.apache.asterix.om.base.ARecord;
+import org.apache.asterix.om.base.AString;
+import org.apache.asterix.om.base.AUUID;
+import org.apache.asterix.om.base.IACursor;
+import org.apache.asterix.om.base.IAObject;
+import org.apache.asterix.om.types.AOrderedListType;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.api.exceptions.ErrorCode;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+
+public class DependencyTupleTranslator extends AbstractTupleTranslator<IEntityRequirements> {
+    // Payload field containing serialized GraphDependency record.
+    private static final int GRAPH_DEPENDENCY_PAYLOAD_TUPLE_FIELD_INDEX = 1;
+
+    // We are interested in the detail of the following records.
+    private static final IRecordTypeDetail DEP_RECORD_DETAIL = GraphixRecordDetailProvider.getDependencyRecordDetail();
+    private static final IRecordTypeDetail GRA_RECORD_DETAIL =
+            GraphixRecordDetailProvider.getGraphDependencyRecordDetail();
+
+    // For constructing our dependency lists.
+    protected OrderedListBuilder listBuilder;
+    protected IARecordBuilder depRecordBuilder;
+    protected AOrderedListType stringList;
+
+    public DependencyTupleTranslator(boolean getTuple) {
+        super(getTuple, GraphixIndexDetailProvider.getGraphDependencyIndexDetail().getExtensionDataset(),
+                GRAPH_DEPENDENCY_PAYLOAD_TUPLE_FIELD_INDEX);
+
+        if (getTuple) {
+            listBuilder = new OrderedListBuilder();
+            depRecordBuilder = new RecordBuilder();
+
+            // Avoid having to create the string list types multiple times.
+            stringList = new AOrderedListType(BuiltinType.ASTRING, null);
+        }
+    }
+
+    @Override
+    protected IEntityRequirements createMetadataEntityFromARecord(ARecord requirements) throws AlgebricksException {
+        // Read in our primary key value.
+        IAObject primaryKeyValueObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_DEPENDENCY_ID);
+        String primaryKeyValueString = ((AString) primaryKeyValueObj).getStringValue();
+        AMutableUUID primaryKeyValue = new AMutableUUID();
+        try {
+            primaryKeyValue.parseUUIDString(primaryKeyValueString.toCharArray(), 0, AUUID.UUID_CHARS);
+
+        } catch (HyracksDataException e) {
+            throw new AlgebricksException(ErrorCode.ILLEGAL_STATE, "Could not extract UUID bytes from ARecord.");
+        }
+
+        // Read in the dataverse name.
+        IAObject dataverseNameObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_DATAVERSE_NAME);
+        DataverseName dataverseName =
+                DataverseName.createFromCanonicalForm(((AString) dataverseNameObj).getStringValue());
+
+        // Read in the entity name.
+        IAObject entityNameObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_ENTITY_NAME);
+        String entityName = ((AString) entityNameObj).getStringValue();
+
+        // Read in the GraphDependency kind.
+        IEntityRequirements.DependentKind entityKind;
+        IAObject entityKindObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_KIND);
+        String entityKindString = ((AString) entityKindObj).getStringValue();
+        if (entityKindString.equals(IEntityRequirements.DependentKind.FUNCTION.toString())) {
+            entityKind = IEntityRequirements.DependentKind.FUNCTION;
+
+        } else if (entityKindString.equals(IEntityRequirements.DependentKind.GRAPH.toString())) {
+            entityKind = IEntityRequirements.DependentKind.GRAPH;
+
+        } else { // entityKindString.equals(IEntityRequirements.DependentKind.VIEW.toString())
+            entityKind = IEntityRequirements.DependentKind.VIEW;
+        }
+
+        // If we have a function, then we must read in the entity detail.
+        String entityDetail = null;
+        if (entityKind == IEntityRequirements.DependentKind.FUNCTION) {
+            IAObject entityDetailObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_ENTITY_DETAIL);
+            entityDetail = ((AString) entityDetailObj).getStringValue();
+        }
+
+        // Read in the dependencies.
+        Set<DependencyIdentifier> dependencyIdentifiers = new HashSet<>();
+        IAObject dependenciesObj = GRA_RECORD_DETAIL.getObjectForField(requirements, FIELD_NAME_DEPENDENCIES);
+        IACursor dependenciesCursor = ((AOrderedList) dependenciesObj).getCursor();
+        while (dependenciesCursor.next()) {
+            ARecord dependency = (ARecord) dependenciesCursor.get();
+
+            // Read in the dataverse name.
+            IAObject depDataverseNameObj = DEP_RECORD_DETAIL.getObjectForField(dependency, FIELD_NAME_DATAVERSE_NAME);
+            DataverseName depDataverseName =
+                    DataverseName.createFromCanonicalForm(((AString) depDataverseNameObj).getStringValue());
+
+            // Read in the entity name.
+            IAObject depEntityNameObj = DEP_RECORD_DETAIL.getObjectForField(dependency, FIELD_NAME_ENTITY_NAME);
+            String depEntityName = ((AString) depEntityNameObj).getStringValue();
+
+            // Read in the entity kind, and add the new dependency identifier.
+            DependencyIdentifier.Kind depEntityKind;
+            IAObject depEntityKindObj = DEP_RECORD_DETAIL.getObjectForField(dependency, FIELD_NAME_KIND);
+            String depEntityKindString = ((AString) depEntityKindObj).getStringValue();
+            if (depEntityKindString.equals(DependencyIdentifier.Kind.GRAPH.toString())) {
+                depEntityKind = DependencyIdentifier.Kind.GRAPH;
+
+            } else if (depEntityKindString.equals(DependencyIdentifier.Kind.DATASET.toString())) {
+                depEntityKind = DependencyIdentifier.Kind.DATASET;
+
+            } else if (depEntityKindString.equals(DependencyIdentifier.Kind.SYNONYM.toString())) {
+                depEntityKind = DependencyIdentifier.Kind.SYNONYM;
+
+            } else { // depEntityKindString.equals(DependencyIdentifier.Kind.FUNCTION.toString())
+                depEntityKind = DependencyIdentifier.Kind.FUNCTION;
+            }
+
+            // If we have a function, then we must read in the entity detail.
+            String depEntityDetail = null;
+            if (depEntityKind == DependencyIdentifier.Kind.FUNCTION) {
+                IAObject depEntityDetailObj = DEP_RECORD_DETAIL.getObjectForField(dependency, FIELD_NAME_ENTITY_DETAIL);
+                depEntityDetail = ((AString) depEntityDetailObj).getStringValue();
+            }
+
+            DependencyIdentifier depIdentifier = (depEntityKind != DependencyIdentifier.Kind.FUNCTION)
+                    ? new DependencyIdentifier(depDataverseName, depEntityName, depEntityKind)
+                    : new DependencyIdentifier(depDataverseName, depEntityName, depEntityDetail, depEntityKind);
+            dependencyIdentifiers.add(depIdentifier);
+        }
+
+        // Return the entity requirements, based on the type.
+        switch (entityKind) {
+            case FUNCTION:
+                int functionArity = Integer.parseInt(entityDetail);
+                FunctionSignature functionSignature = new FunctionSignature(dataverseName, entityName, functionArity);
+                return new FunctionRequirements(functionSignature, dependencyIdentifiers, primaryKeyValue);
+
+            case GRAPH:
+                return new GraphRequirements(dataverseName, entityName, dependencyIdentifiers, primaryKeyValue);
+
+            case VIEW:
+                return new ViewRequirements(dataverseName, entityName, dependencyIdentifiers, primaryKeyValue);
+        }
+        return null;
+    }
+
+    @Override
+    public ITupleReference getTupleFromMetadataEntity(IEntityRequirements requirements) throws HyracksDataException {
+        // TODO (GLENN): There is currently a bug where a metadata dataset index cannot have a UUID primary key field.
+        String primaryKeyValue;
+        try {
+            StringBuilder sb = new StringBuilder();
+            requirements.getPrimaryKeyValue().appendLiteralOnly(sb);
+            primaryKeyValue = sb.toString();
+
+        } catch (IOException e) {
+            throw new HyracksDataException(ErrorCode.ILLEGAL_STATE, "Could not extract UUID bytes from AUUID.");
+        }
+
+        // Write our primary key, which is an autogenerated UUID (we serialize this as a string for our tuple).
+        tupleBuilder.reset();
+        aString.setValue(primaryKeyValue);
+        stringSerde.serialize(aString, tupleBuilder.getDataOutput());
+        tupleBuilder.addFieldEndOffset();
+
+        // Write the payload in the third field of the tuple.
+        recordBuilder.reset(GRA_RECORD_DETAIL.getRecordType());
+
+        // Write our primary key.
+        fieldValue.reset();
+        aString.setValue(primaryKeyValue);
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEPENDENCY_ID), fieldValue);
+
+        // Write the dataverse name.
+        fieldValue.reset();
+        aString.setValue(requirements.getDataverseName().getCanonicalForm());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_DATAVERSE_NAME), fieldValue);
+
+        // Write the entity name.
+        fieldValue.reset();
+        aString.setValue(requirements.getEntityName());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_ENTITY_NAME), fieldValue);
+
+        // Write the entity kind.
+        fieldValue.reset();
+        aString.setValue(requirements.getDependentKind().toString());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_KIND), fieldValue);
+
+        // Write our dependencies list.
+        listBuilder.reset((AOrderedListType) GRA_RECORD_DETAIL.getTypeForField(FIELD_NAME_DEPENDENCIES));
+        for (DependencyIdentifier dependency : requirements) {
+            depRecordBuilder.reset(DEP_RECORD_DETAIL.getRecordType());
+
+            // Write the dependency dataverse.
+            fieldValue.reset();
+            aString.setValue(dependency.getDataverseName().getCanonicalForm());
+            stringSerde.serialize(aString, fieldValue.getDataOutput());
+            depRecordBuilder.addField(DEP_RECORD_DETAIL.getIndexForField(FIELD_NAME_DATAVERSE_NAME), fieldValue);
+
+            // Write the dependency entity name.
+            fieldValue.reset();
+            aString.setValue(dependency.getEntityName());
+            stringSerde.serialize(aString, fieldValue.getDataOutput());
+            depRecordBuilder.addField(DEP_RECORD_DETAIL.getIndexForField(FIELD_NAME_ENTITY_NAME), fieldValue);
+
+            // Write the dependency kind.
+            fieldValue.reset();
+            aString.setValue(dependency.getDependencyKind().toString());
+            stringSerde.serialize(aString, fieldValue.getDataOutput());
+            depRecordBuilder.addField(DEP_RECORD_DETAIL.getIndexForField(FIELD_NAME_KIND), fieldValue);
+
+            // Write the dependency entity detail, if it exists.
+            if (dependency.getDependencyKind() == DependencyIdentifier.Kind.FUNCTION) {
+                fieldValue.reset();
+                aString.setValue(dependency.getEntityDetail());
+                stringSerde.serialize(aString, fieldValue.getDataOutput());
+                depRecordBuilder.addField(DEP_RECORD_DETAIL.getIndexForField(FIELD_NAME_ENTITY_DETAIL), fieldValue);
+            }
+
+            // Write the dependencies record.
+            fieldValue.reset();
+            depRecordBuilder.write(fieldValue.getDataOutput(), true);
+            listBuilder.addItem(fieldValue);
+        }
+        fieldValue.reset();
+        listBuilder.write(fieldValue.getDataOutput(), true);
+        recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEPENDENCIES), fieldValue);
+
+        // Write the entity detail, if it exists.
+        if (requirements.getDependentKind() == IEntityRequirements.DependentKind.FUNCTION) {
+            FunctionRequirements functionRequirements = (FunctionRequirements) requirements;
+            fieldValue.reset();
+            aString.setValue(functionRequirements.getArityAsString());
+            stringSerde.serialize(aString, fieldValue.getDataOutput());
+            recordBuilder.addField(GRA_RECORD_DETAIL.getIndexForField(FIELD_NAME_ENTITY_DETAIL), fieldValue);
+        }
+
+        // Finally, write our record.
+        recordBuilder.write(tupleBuilder.getDataOutput(), true);
+        tupleBuilder.addFieldEndOffset();
+        tuple.reset(tupleBuilder.getFieldEndOffsets(), tupleBuilder.getByteArray());
+        return tuple;
+    }
+}
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 9c83184..8702cd5 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
@@ -16,9 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
 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;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_GRAPH_NAME;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_LABEL;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_SOURCE_KEY;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_SOURCE_LABEL;
+import static org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider.FIELD_NAME_VERTICES;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY;
+
 import java.util.ArrayList;
 import java.util.List;
 
@@ -29,19 +41,23 @@
 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.metadata.bootstrap.GraphixMetadataIndexes;
-import org.apache.asterix.graphix.metadata.bootstrap.GraphixMetadataRecordTypes;
-import org.apache.asterix.graphix.metadata.entities.Graph;
-import org.apache.asterix.graphix.metadata.entities.GraphDependencies;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixIndexDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.GraphixRecordDetailProvider;
+import org.apache.asterix.graphix.metadata.bootstrap.IRecordTypeDetail;
+import org.apache.asterix.graphix.metadata.entity.schema.Edge;
+import org.apache.asterix.graphix.metadata.entity.schema.Graph;
+import org.apache.asterix.graphix.metadata.entity.schema.Schema;
+import org.apache.asterix.graphix.metadata.entity.schema.Vertex;
 import org.apache.asterix.metadata.entitytupletranslators.AbstractTupleTranslator;
 import org.apache.asterix.om.base.AOrderedList;
 import org.apache.asterix.om.base.ARecord;
 import org.apache.asterix.om.base.AString;
 import org.apache.asterix.om.base.IACursor;
+import org.apache.asterix.om.base.IAObject;
 import org.apache.asterix.om.types.AOrderedListType;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
-import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
 import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
@@ -50,181 +66,177 @@
     // Payload field containing serialized Graph.
     private static final int GRAPH_PAYLOAD_TUPLE_FIELD_INDEX = 2;
 
-    // For constructing our dependency, edge, and vertex lists.
+    // 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();
+
+    // For constructing our edge and vertex lists.
     protected OrderedListBuilder listBuilder;
+    protected OrderedListBuilder defListBuilder;
     protected OrderedListBuilder innerListBuilder;
     protected OrderedListBuilder nameListBuilder;
-    protected IARecordBuilder subRecordBuilder;
+    protected IARecordBuilder elemRecordBuilder;
+    protected IARecordBuilder defRecordBuilder;
     protected AOrderedListType stringListList;
     protected AOrderedListType stringList;
 
     public GraphTupleTranslator(boolean getTuple) {
-        super(getTuple, GraphixMetadataIndexes.GRAPH_DATASET, GRAPH_PAYLOAD_TUPLE_FIELD_INDEX);
+        super(getTuple, GraphixIndexDetailProvider.getGraphIndexDetail().getExtensionDataset(),
+                GRAPH_PAYLOAD_TUPLE_FIELD_INDEX);
+
         if (getTuple) {
             listBuilder = new OrderedListBuilder();
+            defListBuilder = new OrderedListBuilder();
             innerListBuilder = new OrderedListBuilder();
             nameListBuilder = new OrderedListBuilder();
-            subRecordBuilder = new RecordBuilder();
+            elemRecordBuilder = new RecordBuilder();
+            defRecordBuilder = new RecordBuilder();
+
+            // Avoid having to create the string list types multiple times.
             stringListList = new AOrderedListType(new AOrderedListType(BuiltinType.ASTRING, null), null);
             stringList = new AOrderedListType(BuiltinType.ASTRING, null);
         }
     }
 
     @Override
-    protected Graph createMetadataEntityFromARecord(ARecord graphRecord) throws AlgebricksException {
+    protected Graph createMetadataEntityFromARecord(ARecord graph) throws AlgebricksException {
         // Read in the dataverse name.
-        DataverseName dataverseName = DataverseName.createFromCanonicalForm(((AString) graphRecord
-                .getValueByPos(GraphixMetadataRecordTypes.GRAPH_ARECORD_DATAVERSENAME_FIELD_INDEX)).getStringValue());
+        IAObject dataverseNameObj = GRAPH_RECORD_DETAIL.getObjectForField(graph, FIELD_NAME_DATAVERSE_NAME);
+        DataverseName dataverseName =
+                DataverseName.createFromCanonicalForm(((AString) dataverseNameObj).getStringValue());
 
         // Read in the graph name.
-        String graphName =
-                ((AString) graphRecord.getValueByPos(GraphixMetadataRecordTypes.GRAPH_ARECORD_GRAPHNAME_FIELD_INDEX))
-                        .getStringValue();
-
-        // Read in the dependencies.
-        IACursor dependenciesCursor = ((AOrderedList) graphRecord
-                .getValueByPos(GraphixMetadataRecordTypes.GRAPH_ARECORD_DEPENDENCIES_FIELD_INDEX)).getCursor();
-        List<List<Triple<DataverseName, String, String>>> graphDependencies = new ArrayList<>();
-        while (dependenciesCursor.next()) {
-            List<Triple<DataverseName, String, String>> dependencyList = new ArrayList<>();
-            IACursor qualifiedDependencyCursor = ((AOrderedList) dependenciesCursor.get()).getCursor();
-            while (qualifiedDependencyCursor.next()) {
-                Triple<DataverseName, String, String> dependency =
-                        getDependency((AOrderedList) qualifiedDependencyCursor.get());
-                dependencyList.add(dependency);
-            }
-            graphDependencies.add(dependencyList);
-        }
+        IAObject graphNameObj = GRAPH_RECORD_DETAIL.getObjectForField(graph, FIELD_NAME_GRAPH_NAME);
+        String graphName = ((AString) graphNameObj).getStringValue();
 
         // Read in the vertex and edge lists.
         GraphIdentifier graphIdentifier = new GraphIdentifier(dataverseName, graphName);
-        Graph.Schema graphSchema = readGraphSchema(graphRecord, graphIdentifier);
-        return new Graph(graphIdentifier, graphSchema, new GraphDependencies(graphDependencies));
+        Schema graphSchema = readGraphSchema(graph, graphIdentifier);
+        return new Graph(graphIdentifier, graphSchema);
     }
 
-    private Graph.Schema readGraphSchema(ARecord graphRecord, GraphIdentifier graphIdentifier) throws AsterixException {
-        Graph.Schema.Builder schemaBuilder = new Graph.Schema.Builder(graphIdentifier);
+    private Schema readGraphSchema(ARecord graph, GraphIdentifier graphIdentifier) throws AsterixException {
+        Schema.Builder schemaBuilder = new Schema.Builder(graphIdentifier);
 
         // Read in the vertex list.
-        IACursor verticesCursor = ((AOrderedList) graphRecord
-                .getValueByPos(GraphixMetadataRecordTypes.GRAPH_ARECORD_VERTICES_FIELD_INDEX)).getCursor();
+        IAObject verticesObj = GRAPH_RECORD_DETAIL.getObjectForField(graph, FIELD_NAME_VERTICES);
+        IACursor verticesCursor = ((AOrderedList) verticesObj).getCursor();
         while (verticesCursor.next()) {
             ARecord vertex = (ARecord) verticesCursor.get();
 
             // Read in the label name.
-            String labelName = ((AString) vertex
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_LABEL_FIELD_INDEX))
-                            .getStringValue();
+            IAObject labelNameObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_LABEL);
+            ElementLabel vertexLabel = new ElementLabel(((AString) labelNameObj).getStringValue());
 
-            // Read in the primary key fields.
-            List<List<String>> primaryKeyFields = new ArrayList<>();
-            IACursor primaryKeyCursor = ((AOrderedList) vertex
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_PRIMARY_KEY_FIELD_INDEX))
-                            .getCursor();
-            while (primaryKeyCursor.next()) {
-                IACursor nameCursor = ((AOrderedList) primaryKeyCursor.get()).getCursor();
-                primaryKeyFields.add(readNameList(nameCursor));
-            }
-
-            // Read in the vertex definition(s). Validate each definition.
-            IACursor definitionsCursor = ((AOrderedList) vertex
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_DEFINITIONS_FIELD_INDEX))
-                            .getCursor();
+            // Read in our vertex definitions.
+            IAObject definitionsObj = VERTEX_RECORD_DETAIL.getObjectForField(vertex, FIELD_NAME_DEFINITIONS);
+            IACursor definitionsCursor = ((AOrderedList) definitionsObj).getCursor();
             while (definitionsCursor.next()) {
-                String definition = ((AString) definitionsCursor.get()).getStringValue();
-                schemaBuilder.addVertex(labelName, primaryKeyFields, definition);
+                ARecord definition = (ARecord) definitionsCursor.get();
+
+                // 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 = 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;
 
                     case CONFLICTING_PRIMARY_KEY:
                         throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Conflicting primary keys for vertices with label " + labelName);
+                                "Conflicting primary keys for vertices with label " + vertexLabel);
 
                     default:
                         throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Schema 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 primary key!");
                 }
             }
         }
 
         // Read in the edge list.
-        IACursor edgesCursor =
-                ((AOrderedList) graphRecord.getValueByPos(GraphixMetadataRecordTypes.GRAPH_ARECORD_EDGES_FIELD_INDEX))
-                        .getCursor();
+        IAObject edgesObj = GRAPH_RECORD_DETAIL.getObjectForField(graph, FIELD_NAME_EDGES);
+        IACursor edgesCursor = ((AOrderedList) edgesObj).getCursor();
         while (edgesCursor.next()) {
             ARecord edge = (ARecord) edgesCursor.get();
 
             // Read in the label name.
-            String labelName =
-                    ((AString) edge.getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_LABEL_FIELD_INDEX))
-                            .getStringValue();
+            IAObject labelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_LABEL);
+            ElementLabel edgeLabel = new ElementLabel(((AString) labelNameObj).getStringValue());
 
             // Read in the destination label name.
-            String destinationLabelName = ((AString) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEST_LABEL_FIELD_INDEX))
-                            .getStringValue();
+            IAObject destinationLabelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_DESTINATION_LABEL);
+            ElementLabel destinationLabel = new ElementLabel(((AString) destinationLabelNameObj).getStringValue());
 
             // Read in the source label name.
-            String sourceLabelName = ((AString) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_SOURCE_LABEL_FIELD_INDEX))
-                            .getStringValue();
+            IAObject sourceLabelNameObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_SOURCE_LABEL);
+            ElementLabel sourceLabel = new ElementLabel(((AString) sourceLabelNameObj).getStringValue());
 
-            // Read in the primary key fields.
-            List<List<String>> primaryKeyFields = new ArrayList<>();
-            IACursor primaryKeyCursor = ((AOrderedList) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_PRIMARY_KEY_FIELD_INDEX)).getCursor();
-            while (primaryKeyCursor.next()) {
-                IACursor nameCursor = ((AOrderedList) primaryKeyCursor.get()).getCursor();
-                primaryKeyFields.add(readNameList(nameCursor));
-            }
-
-            // Read in the destination key fields.
-            List<List<String>> destinationKeyFields = new ArrayList<>();
-            IACursor destinationKeyCursor = ((AOrderedList) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEST_KEY_FIELD_INDEX)).getCursor();
-            while (destinationKeyCursor.next()) {
-                IACursor nameCursor = ((AOrderedList) destinationKeyCursor.get()).getCursor();
-                destinationKeyFields.add(readNameList(nameCursor));
-            }
-
-            // Read in the source key fields.
-            List<List<String>> sourceKeyFields = new ArrayList<>();
-            IACursor sourceKeyCursor = ((AOrderedList) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_SOURCE_KEY_FIELD_INDEX)).getCursor();
-            while (sourceKeyCursor.next()) {
-                IACursor nameCursor = ((AOrderedList) sourceKeyCursor.get()).getCursor();
-                sourceKeyFields.add(readNameList(nameCursor));
-            }
-
-            // Read in the edge definition(s). Validate each definition.
-            IACursor definitionsCursor = ((AOrderedList) edge
-                    .getValueByPos(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEFINITIONS_FIELD_INDEX)).getCursor();
+            // Read in our edge definitions.
+            IAObject definitionsObj = EDGE_RECORD_DETAIL.getObjectForField(edge, FIELD_NAME_DEFINITIONS);
+            IACursor definitionsCursor = ((AOrderedList) definitionsObj).getCursor();
             while (definitionsCursor.next()) {
-                String definition = ((AString) definitionsCursor.get()).getStringValue();
-                schemaBuilder.addEdge(labelName, destinationLabelName, sourceLabelName, primaryKeyFields,
-                        destinationKeyFields, sourceKeyFields, definition);
+                ARecord definition = (ARecord) definitionsCursor.get();
+
+                // 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 =
+                        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 = 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;
 
                     case SOURCE_VERTEX_NOT_FOUND:
                         throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Source vertex " + sourceLabelName + " not found in the edge " + labelName + ".");
+                                "Source vertex " + sourceLabel + " not found in the edge " + edgeLabel + ".");
 
                     case DESTINATION_VERTEX_NOT_FOUND:
-                        throw new AsterixException(ErrorCode.METADATA_ERROR, "Destination vertex "
-                                + destinationLabelName + " not found in the edge " + labelName + ".");
-
-                    case CONFLICTING_PRIMARY_KEY:
-                    case CONFLICTING_SOURCE_VERTEX:
-                    case CONFLICTING_DESTINATION_VERTEX:
                         throw new AsterixException(ErrorCode.METADATA_ERROR,
-                                "Conflicting edge with the same label found: " + labelName);
+                                "Destination vertex " + destinationLabel + " not found in the edge " + 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,
-                                "Schema edge was not returned, and an unexpected error encountered");
+                                "Edge constructor was not returned, and an unexpected error encountered");
                 }
             }
         }
@@ -254,63 +266,40 @@
         tupleBuilder.addFieldEndOffset();
 
         // Write the payload in the third field of the tuple.
-        recordBuilder.reset(GraphixMetadataRecordTypes.GRAPH_RECORDTYPE);
+        recordBuilder.reset(GRAPH_RECORD_DETAIL.getRecordType());
 
         // Write the dataverse name.
         fieldValue.reset();
         aString.setValue(dataverseCanonicalName);
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        recordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_ARECORD_DATAVERSENAME_FIELD_INDEX, fieldValue);
+        recordBuilder.addField(GRAPH_RECORD_DETAIL.getIndexForField(FIELD_NAME_DATAVERSE_NAME), fieldValue);
 
         // Write the graph name.
         fieldValue.reset();
         aString.setValue(graph.getGraphName());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        recordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_ARECORD_GRAPHNAME_FIELD_INDEX, fieldValue);
-
-        // Write our dependencies.
-        ArrayBackedValueStorage itemValue = new ArrayBackedValueStorage();
-        listBuilder.reset((AOrderedListType) GraphixMetadataRecordTypes.GRAPH_RECORDTYPE
-                .getFieldTypes()[GraphixMetadataRecordTypes.GRAPH_ARECORD_DEPENDENCIES_FIELD_INDEX]);
-        for (List<Triple<DataverseName, String, String>> dependencies : graph.getDependencies()
-                .getListRepresentation()) {
-            List<String> subNames = new ArrayList<>();
-            innerListBuilder.reset(stringListList);
-            for (Triple<DataverseName, String, String> dependency : dependencies) {
-                subNames.clear();
-                getDependencySubNames(dependency, subNames);
-                writeNameList(subNames, itemValue);
-                innerListBuilder.addItem(itemValue);
-            }
-            itemValue.reset();
-            innerListBuilder.write(itemValue.getDataOutput(), true);
-            listBuilder.addItem(itemValue);
-        }
-        fieldValue.reset();
-        listBuilder.write(fieldValue.getDataOutput(), true);
-        recordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_ARECORD_DEPENDENCIES_FIELD_INDEX, fieldValue);
+        recordBuilder.addField(GRAPH_RECORD_DETAIL.getIndexForField(FIELD_NAME_GRAPH_NAME), fieldValue);
 
         // Write our vertex set.
-        listBuilder.reset((AOrderedListType) GraphixMetadataRecordTypes.GRAPH_RECORDTYPE
-                .getFieldTypes()[GraphixMetadataRecordTypes.GRAPH_ARECORD_VERTICES_FIELD_INDEX]);
-        for (Graph.Vertex vertex : graph.getGraphSchema().getVertices()) {
+        ArrayBackedValueStorage itemValue = new ArrayBackedValueStorage();
+        listBuilder.reset((AOrderedListType) GRAPH_RECORD_DETAIL.getTypeForField(FIELD_NAME_VERTICES));
+        for (Vertex vertex : graph.getGraphSchema().getVertices()) {
             writeVertexRecord(vertex, itemValue);
             listBuilder.addItem(itemValue);
         }
         fieldValue.reset();
         listBuilder.write(fieldValue.getDataOutput(), true);
-        recordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_ARECORD_VERTICES_FIELD_INDEX, fieldValue);
+        recordBuilder.addField(GRAPH_RECORD_DETAIL.getIndexForField(FIELD_NAME_VERTICES), fieldValue);
 
         // Write our edge set.
-        listBuilder.reset((AOrderedListType) GraphixMetadataRecordTypes.GRAPH_RECORDTYPE
-                .getFieldTypes()[GraphixMetadataRecordTypes.GRAPH_ARECORD_EDGES_FIELD_INDEX]);
-        for (Graph.Edge edge : graph.getGraphSchema().getEdges()) {
+        listBuilder.reset((AOrderedListType) GRAPH_RECORD_DETAIL.getTypeForField(FIELD_NAME_EDGES));
+        for (Edge edge : graph.getGraphSchema().getEdges()) {
             writeEdgeRecord(edge, itemValue);
             listBuilder.addItem(itemValue);
         }
         fieldValue.reset();
         listBuilder.write(fieldValue.getDataOutput(), true);
-        recordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_ARECORD_EDGES_FIELD_INDEX, fieldValue);
+        recordBuilder.addField(GRAPH_RECORD_DETAIL.getIndexForField(FIELD_NAME_EDGES), fieldValue);
 
         // Finally, write our record.
         recordBuilder.write(tupleBuilder.getDataOutput(), true);
@@ -319,108 +308,110 @@
         return tuple;
     }
 
-    private void writeVertexRecord(Graph.Vertex vertex, ArrayBackedValueStorage itemValue) throws HyracksDataException {
-        subRecordBuilder.reset(GraphixMetadataRecordTypes.VERTICES_RECORDTYPE);
+    private void writeVertexRecord(Vertex vertex, ArrayBackedValueStorage itemValue) throws HyracksDataException {
+        elemRecordBuilder.reset(VERTEX_RECORD_DETAIL.getRecordType());
 
         // Write the label name.
         fieldValue.reset();
-        aString.setValue(vertex.getLabelName());
+        aString.setValue(vertex.getLabel().toString());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_LABEL_FIELD_INDEX, fieldValue);
-
-        // Write the primary key fields.
-        fieldValue.reset();
-        innerListBuilder.reset(stringListList);
-        for (List<String> keyField : vertex.getPrimaryKeyFieldNames()) {
-            writeNameList(keyField, itemValue);
-            innerListBuilder.addItem(itemValue);
-        }
-        innerListBuilder.write(fieldValue.getDataOutput(), true);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_PRIMARY_KEY_FIELD_INDEX,
-                fieldValue);
+        elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_LABEL), fieldValue);
 
         // Write the vertex definition(s).
-        fieldValue.reset();
-        innerListBuilder.reset(stringList);
-        for (String definition : vertex.getDefinitions()) {
-            itemValue.reset();
-            aString.setValue(definition);
-            stringSerde.serialize(aString, itemValue.getDataOutput());
-            innerListBuilder.addItem(itemValue);
+        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);
         }
-        innerListBuilder.write(fieldValue.getDataOutput(), true);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_VERTICES_ARECORD_DEFINITIONS_FIELD_INDEX,
-                fieldValue);
+        fieldValue.reset();
+        defListBuilder.write(fieldValue.getDataOutput(), true);
+        elemRecordBuilder.addField(VERTEX_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEFINITIONS), fieldValue);
 
         itemValue.reset();
-        subRecordBuilder.write(itemValue.getDataOutput(), true);
+        elemRecordBuilder.write(itemValue.getDataOutput(), true);
     }
 
-    private void writeEdgeRecord(Graph.Edge edge, ArrayBackedValueStorage itemValue) throws HyracksDataException {
-        subRecordBuilder.reset(GraphixMetadataRecordTypes.EDGES_RECORDTYPE);
+    private void writeEdgeRecord(Edge edge, ArrayBackedValueStorage itemValue) throws HyracksDataException {
+        elemRecordBuilder.reset(EDGE_RECORD_DETAIL.getRecordType());
 
         // Write the label name.
         fieldValue.reset();
-        aString.setValue(edge.getLabelName());
+        aString.setValue(edge.getLabel().toString());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_LABEL_FIELD_INDEX, fieldValue);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_LABEL), fieldValue);
 
         // Write the destination label name.
         fieldValue.reset();
-        aString.setValue(edge.getDestinationLabelName());
+        aString.setValue(edge.getDestinationLabel().toString());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEST_LABEL_FIELD_INDEX, fieldValue);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_DESTINATION_LABEL), fieldValue);
 
         // Write the source label name.
         fieldValue.reset();
-        aString.setValue(edge.getSourceLabelName());
+        aString.setValue(edge.getSourceLabel().toString());
         stringSerde.serialize(aString, fieldValue.getDataOutput());
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_SOURCE_LABEL_FIELD_INDEX, fieldValue);
-
-        // Write the primary key fields.
-        fieldValue.reset();
-        innerListBuilder.reset(stringListList);
-        for (List<String> keyField : edge.getPrimaryKeyFieldNames()) {
-            writeNameList(keyField, itemValue);
-            innerListBuilder.addItem(itemValue);
-        }
-        innerListBuilder.write(fieldValue.getDataOutput(), true);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_PRIMARY_KEY_FIELD_INDEX, 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);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEST_KEY_FIELD_INDEX, fieldValue);
-
-        // Write the source key fields.
-        fieldValue.reset();
-        innerListBuilder.reset(stringListList);
-        for (List<String> keyField : edge.getSourceKeyFieldNames()) {
-            writeNameList(keyField, itemValue);
-            innerListBuilder.addItem(itemValue);
-        }
-        innerListBuilder.write(fieldValue.getDataOutput(), true);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_SOURCE_KEY_FIELD_INDEX, fieldValue);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_SOURCE_LABEL), fieldValue);
 
         // Write the edge definition(s).
-        fieldValue.reset();
-        innerListBuilder.reset(stringList);
-        for (String definition : edge.getDefinitions()) {
-            itemValue.reset();
-            aString.setValue(definition);
-            stringSerde.serialize(aString, itemValue.getDataOutput());
-            innerListBuilder.addItem(itemValue);
+        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);
         }
-        innerListBuilder.write(fieldValue.getDataOutput(), true);
-        subRecordBuilder.addField(GraphixMetadataRecordTypes.GRAPH_EDGES_ARECORD_DEFINITIONS_FIELD_INDEX, fieldValue);
+        fieldValue.reset();
+        defListBuilder.write(fieldValue.getDataOutput(), true);
+        elemRecordBuilder.addField(EDGE_RECORD_DETAIL.getIndexForField(FIELD_NAME_DEFINITIONS), fieldValue);
 
         itemValue.reset();
-        subRecordBuilder.write(itemValue.getDataOutput(), true);
+        elemRecordBuilder.write(itemValue.getDataOutput(), true);
     }
 
     private void writeNameList(List<String> name, ArrayBackedValueStorage itemValue) throws HyracksDataException {
diff --git a/asterix-graphix/src/main/resources/lang-extension/lang.txt b/asterix-graphix/src/main/resources/lang-extension/lang.txt
index 6d215f0..12643bf 100644
--- a/asterix-graphix/src/main/resources/lang-extension/lang.txt
+++ b/asterix-graphix/src/main/resources/lang-extension/lang.txt
@@ -17,11 +17,24 @@
 // under the License.
 //
 
+import java.util.HashSet;
+import java.util.Set;
+
 import org.apache.asterix.graphix.common.metadata.GraphElementIdentifier;
+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.IGraphExpr;
+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.statement.CreateGraphStatement;
 import org.apache.asterix.graphix.lang.statement.GraphDropStatement;
 import org.apache.asterix.graphix.lang.statement.GraphElementDecl;
+import org.apache.asterix.graphix.lang.struct.EdgeDescriptor;
+import org.apache.asterix.graphix.lang.struct.ElementLabel;
 import org.apache.asterix.lang.sqlpp.parser.ParseException;
 import org.apache.asterix.lang.sqlpp.parser.SqlppParseException;
 import org.apache.asterix.lang.sqlpp.parser.Token;
@@ -45,6 +58,102 @@
     });
 }
 
+@override
+SelectBlock SelectBlock() throws ParseException:
+{
+  SelectClause selectClause = null;
+  FromClause fromClause = null;
+  FromGraphClause fromGraphClause = null;
+  List<LetClause> fromLetClauses = null;
+  WhereClause whereClause = null;
+  GroupbyClause groupbyClause = null;
+  List<LetClause> gbyLetClauses = null;
+  HavingClause havingClause = null;
+  SourceLocation startSrcLoc = null;
+
+  List<AbstractClause> fromLetWhereClauses = new ArrayList<AbstractClause>();
+  List<AbstractClause> gbyLetHavingClauses = new ArrayList<AbstractClause>();
+}
+{
+  (
+    (
+      selectClause = SelectClause() { startSrcLoc = selectClause.getSourceLocation(); }
+      (
+        (
+          ( LOOKAHEAD(2)
+            fromGraphClause = FromGraphClause()
+            | fromClause = FromClause()
+          )
+          ( fromLetClauses = LetClause() )?
+          ( whereClause = WhereClause() )?
+          ( groupbyClause = GroupbyClause()
+            ( gbyLetClauses = LetClause() )?
+            ( havingClause = HavingClause() )? )?
+        )
+        |
+        ( fromLetClauses = LetClause()
+          {
+            // LET without FROM -> create dummy FROM clause: FROM {{missing}} AS #0
+            SourceLocation sourceLoc = getSourceLocation(token);
+            LiteralExpr missingExpr = new LiteralExpr(MissingLiteral.INSTANCE);
+            missingExpr.setSourceLocation(sourceLoc);
+            List<Expression> list = new ArrayList<Expression>(1);
+            list.add(missingExpr);
+            ListConstructor listExpr = new ListConstructor(ListConstructor.Type.ORDERED_LIST_CONSTRUCTOR, list);
+            listExpr.setSourceLocation(sourceLoc);
+            List<FromTerm> fromTerms = new ArrayList<FromTerm>(1);
+            VariableExpr fromVar = new VariableExpr(new VarIdentifier("#0"));
+            fromVar.setSourceLocation(sourceLoc);
+            fromTerms.add(new FromTerm(listExpr, fromVar, null, new ArrayList<AbstractBinaryCorrelateClause>()));
+            fromClause = new FromClause(fromTerms);
+          }
+          ( whereClause = WhereClause() )?
+        )
+      )?
+    )
+    |
+    (
+      ( LOOKAHEAD(2)
+        fromGraphClause = FromGraphClause() { startSrcLoc = fromGraphClause.getSourceLocation(); }
+        | fromClause = FromClause() { startSrcLoc = fromClause.getSourceLocation(); }
+     )
+     ( fromLetClauses = LetClause() )?
+     ( whereClause = WhereClause() )?
+     ( groupbyClause = GroupbyClause()
+       ( gbyLetClauses = LetClause() )?
+       ( havingClause = HavingClause() )? )?
+     selectClause = SelectClause()
+    )
+  )
+  {
+    if (fromLetClauses != null) {
+      fromLetWhereClauses.addAll(fromLetClauses);
+    }
+    if (whereClause != null) {
+      fromLetWhereClauses.add(whereClause);
+    }
+    if (gbyLetClauses != null) {
+      gbyLetHavingClauses.addAll(gbyLetClauses);
+    }
+    if (havingClause != null) {
+      gbyLetHavingClauses.add(havingClause);
+    }
+
+    if (fromClause != null) {
+      SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, fromLetWhereClauses,
+        groupbyClause, gbyLetHavingClauses);
+      selectBlock.setSourceLocation(startSrcLoc);
+      return selectBlock;
+
+    } else {
+      GraphSelectBlock selectBlock = new GraphSelectBlock(selectClause, fromGraphClause,
+        fromLetWhereClauses, groupbyClause, gbyLetHavingClauses);
+      selectBlock.setSourceLocation(startSrcLoc);
+      return selectBlock;
+    }
+  }
+}
+
 @merge
 Statement CreateStatement() throws ParseException:
 {
@@ -176,36 +285,36 @@
 @new
 GraphConstructor GraphConstructor(Token startStmtToken) throws ParseException:
 {
-  List<GraphConstructor.VertexElement> vertexElements = new ArrayList<GraphConstructor.VertexElement>();
-  List<GraphConstructor.EdgeElement> edgeElements = new ArrayList<GraphConstructor.EdgeElement>();
-  GraphConstructor.VertexElement vertexElement = null;
-  GraphConstructor.EdgeElement edgeElement = null;
+  List<GraphConstructor.VertexConstructor> vertexConstructors = new ArrayList<GraphConstructor.VertexConstructor>();
+  List<GraphConstructor.EdgeConstructor> edgeConstructors = new ArrayList<GraphConstructor.EdgeConstructor>();
+  GraphConstructor.VertexConstructor vertexConstructor = null;
+  GraphConstructor.EdgeConstructor edgeConstructor = null;
 }
 {
-  vertexElement = GraphVertexSpecification(startStmtToken) { vertexElements.add(vertexElement); }
+  vertexConstructor = GraphVertexSpecification(startStmtToken) { vertexConstructors.add(vertexConstructor); }
   ( <COMMA>
     (
-      ( vertexElement = GraphVertexSpecification(token) { vertexElements.add(vertexElement); } )
-      | ( edgeElement = GraphEdgeSpecification(token) { edgeElements.add(edgeElement); } )
+      ( vertexConstructor = GraphVertexSpecification(token) { vertexConstructors.add(vertexConstructor); } )
+      | ( edgeConstructor = GraphEdgeSpecification(token) { edgeConstructors.add(edgeConstructor); } )
     )
   )*
   {
-    GraphConstructor graphConstructor = new GraphConstructor(vertexElements, edgeElements);
+    GraphConstructor graphConstructor = new GraphConstructor(vertexConstructors, edgeConstructors);
     return addSourceLocation(graphConstructor, startStmtToken);
   }
 }
 
 @new
-GraphConstructor.VertexElement GraphVertexSpecification(Token startStmtToken) throws ParseException:
+GraphConstructor.VertexConstructor GraphVertexSpecification(Token startStmtToken) throws ParseException:
 {
   Pair<List<Integer>, List<List<String>>> primaryKeyFields;
   Token beginPos = null, endPos = null;
   Expression vertexDefinitionExpr;
-  String vertexName;
+  ElementLabel vertexLabel;
 }
 {
   <VERTEX>
-  vertexName = GraphVertexDefinitionPattern()
+  vertexLabel = GraphVertexDefinitionPattern()
   <PRIMARY> <KEY> <LEFTPAREN> primaryKeyFields = KeyFields() <RIGHTPAREN>
   <AS>
   {
@@ -213,91 +322,74 @@
     createNewScope();
   }
   (
-    vertexDefinitionExpr = ViewBody()
-    | <LEFTPAREN> vertexDefinitionExpr = ViewBody() <RIGHTPAREN>
+    vertexDefinitionExpr = ViewBody() { endPos = token; }
+    | <LEFTPAREN> { beginPos = token; } vertexDefinitionExpr = ViewBody() { endPos = token; } <RIGHTPAREN>
   )
   {
-    endPos = token;
     String vDef = extractFragment(beginPos.beginLine, beginPos.beginColumn + 1, endPos.endLine, endPos.endColumn + 1);
     removeCurrentScope();
-    GraphConstructor.VertexElement vertexElement = new GraphConstructor.VertexElement(vertexName,
+    GraphConstructor.VertexConstructor vertexConstructor = new GraphConstructor.VertexConstructor(vertexLabel,
       primaryKeyFields.second, primaryKeyFields.first, vertexDefinitionExpr, vDef);
-    return addSourceLocation(vertexElement, startStmtToken);
+    return addSourceLocation(vertexConstructor, startStmtToken);
   }
 }
 
 @new
-String GraphVertexDefinitionPattern() throws ParseException:
+ElementLabel GraphVertexDefinitionPattern() throws ParseException:
 {
   String vertexName;
 }
 {
   <LEFTPAREN> <COLON> vertexName = Identifier() <RIGHTPAREN>
   {
-    return vertexName;
+    return new ElementLabel(vertexName);
   }
 }
 
 @new
-GraphConstructor.EdgeElement GraphEdgeSpecification(Token startStmtToken) throws ParseException:
+GraphConstructor.EdgeConstructor GraphEdgeSpecification(Token startStmtToken) throws ParseException:
 {
-  Pair<Triple<String, String, String>, Boolean> edgeDefinitionPattern;
+  Pair<Triple<ElementLabel, ElementLabel, ElementLabel>, Boolean> edgeDefinitionPattern;
   Pair<List<Integer>, List<List<String>>> keyFields;
   Token beginPos = null, endPos = null;
   Expression edgeDefinitionExpr = null;
 
   List<Integer> destinationKeySourceIndicators = null;
   List<Integer> sourceKeySourceIndicators = null;
-  List<Integer> primaryKeySourceIndicators = null;
   List<List<String>> destinationKeyFields = null;
   List<List<String>> sourceKeyFields = null;
-  List<List<String>> primaryKeyFields = null;
 }
 {
   <EDGE>
   edgeDefinitionPattern = GraphEdgeDefinitionPattern()
   (
+    <SOURCE> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
+    {
+      sourceKeyFields = keyFields.second;
+      sourceKeySourceIndicators = keyFields.first;
+    }
+    <DESTINATION> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
+    {
+      destinationKeyFields = keyFields.second;
+      destinationKeySourceIndicators = keyFields.first;
+    }
+    <AS>
+    {
+      beginPos = token;
+      createNewScope();
+    }
     (
-      <PRIMARY> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
-      {
-        primaryKeyFields = keyFields.second;
-        primaryKeySourceIndicators = keyFields.first;
-      }
-      <SOURCE> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
-      {
-        sourceKeyFields = keyFields.second;
-        sourceKeySourceIndicators = keyFields.first;
-      }
-      <DESTINATION> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
-      {
-        destinationKeyFields = keyFields.second;
-        destinationKeySourceIndicators = keyFields.first;
-      }
-      <AS>
-      {
-        beginPos = token;
-        createNewScope();
-      }
-      (
-        edgeDefinitionExpr = ViewBody()
-        | <LEFTPAREN> edgeDefinitionExpr = ViewBody() <RIGHTPAREN>
-      )
-    )
-    |
-    (
-      <DESTINATION> <KEY> <LEFTPAREN> keyFields = KeyFields() <RIGHTPAREN>
-      {
-        destinationKeyFields = keyFields.second;
-        destinationKeySourceIndicators = keyFields.first;
-      }
+      edgeDefinitionExpr = SelectExpression(true) { endPos = token; }
+      | <LEFTPAREN> { beginPos = token; } edgeDefinitionExpr = SelectExpression(true) { endPos = token; } <RIGHTPAREN>
     )
   )
   {
-    String destinationLabel, edgeLabel, sourceLabel;
+    ElementLabel destinationLabel, edgeLabel, sourceLabel;
     if (edgeDefinitionPattern.second) { // isDirectedLeft
       sourceLabel = edgeDefinitionPattern.first.third;
       edgeLabel = edgeDefinitionPattern.first.second;
       destinationLabel = edgeDefinitionPattern.first.first;
+
     } else {
       sourceLabel = edgeDefinitionPattern.first.first;
       edgeLabel = edgeDefinitionPattern.first.second;
@@ -306,32 +398,285 @@
 
     String eDef = null;
     if (edgeDefinitionExpr != null) {
-      endPos = token;
       eDef = extractFragment(beginPos.beginLine, beginPos.beginColumn + 1, endPos.endLine, endPos.endColumn + 1);
       removeCurrentScope();
     }
 
-    GraphConstructor.EdgeElement edgeElement = new GraphConstructor.EdgeElement(edgeLabel, destinationLabel,
-        sourceLabel, primaryKeyFields, primaryKeySourceIndicators, destinationKeyFields, destinationKeySourceIndicators,
-        sourceKeyFields, sourceKeySourceIndicators, edgeDefinitionExpr, eDef);
-    return addSourceLocation(edgeElement, startStmtToken);
+    GraphConstructor.EdgeConstructor edgeConstructor = new GraphConstructor.EdgeConstructor(edgeLabel, destinationLabel,
+        sourceLabel, destinationKeyFields, destinationKeySourceIndicators, sourceKeyFields, sourceKeySourceIndicators,
+        edgeDefinitionExpr, eDef);
+    return addSourceLocation(edgeConstructor, startStmtToken);
   }
 }
 
 @new
-Pair<Triple<String, String, String>, Boolean> GraphEdgeDefinitionPattern() throws ParseException:
+Pair<Triple<ElementLabel, ElementLabel, ElementLabel>, Boolean> GraphEdgeDefinitionPattern() throws ParseException:
 {
-  String leftVertexName, edgeName, rightVertexName;
+  ElementLabel leftVertexLabel, rightVertexLabel;
   boolean isDirectedLeft;
+  String edgeName;
 }
 {
-  leftVertexName = GraphVertexDefinitionPattern()
+  leftVertexLabel = GraphVertexDefinitionPattern()
   ( <MINUS> <LEFTBRACKET> <COLON> edgeName = Identifier() <RIGHTBRACKET> <MINUS> <GT> { isDirectedLeft = false; }
   | <LT> <MINUS> <LEFTBRACKET> <COLON> edgeName = Identifier() <RIGHTBRACKET> <MINUS> { isDirectedLeft = true; } )
-  rightVertexName = GraphVertexDefinitionPattern()
+  rightVertexLabel = GraphVertexDefinitionPattern()
   {
-    Triple<String, String, String> t = new Triple<String, String, String>(leftVertexName, edgeName, rightVertexName);
-    return new Pair<Triple<String, String, String>, Boolean>(t, isDirectedLeft);
+    Triple<ElementLabel, ElementLabel, ElementLabel> t = new Triple<ElementLabel, ElementLabel, ElementLabel>(
+      leftVertexLabel, new ElementLabel(edgeName), rightVertexLabel);
+    return new Pair<Triple<ElementLabel, ElementLabel, ElementLabel>, Boolean>(t, isDirectedLeft);
+  }
+}
+
+@new
+FromGraphClause FromGraphClause() throws ParseException:
+{
+  Token startToken = null;
+  GraphConstructor graphConstructor = null;
+  Pair<DataverseName, Identifier> nameComponents = null;
+  AbstractBinaryCorrelateClause correlateClause = null;
+
+  List<MatchClause> matchClauses = new ArrayList<MatchClause>();
+  List<PathPatternExpr> pathPatternExpressions = null;
+  List<AbstractBinaryCorrelateClause> correlateClauses = new ArrayList<AbstractBinaryCorrelateClause>();
+}
+{
+  <FROM> <GRAPH> { startToken = token; }
+  (
+    graphConstructor = GraphConstructor(token)
+    | nameComponents = QualifiedName()
+  )
+  <MATCH> pathPatternExpressions = PathPatternExpressions()
+  { matchClauses.add(new MatchClause(pathPatternExpressions, MatchType.LEADING)); }
+  ( LOOKAHEAD(3) // We want to avoid getting confused with the correlated clauses below.
+    (
+      <LEFT> ( <OUTER> )? <MATCH> pathPatternExpressions = PathPatternExpressions()
+      { matchClauses.add(new MatchClause(pathPatternExpressions, MatchType.LEFTOUTER)); }
+      |
+      ( <INNER> )? <MATCH> pathPatternExpressions = PathPatternExpressions()
+      { matchClauses.add(new MatchClause(pathPatternExpressions, MatchType.INNER)); }
+    )
+  )*
+  (
+    (
+      correlateClause = JoinOrUnnestClause(JoinType.INNER, UnnestType.INNER)
+      | ( <INNER> correlateClause = JoinOrUnnestClause(JoinType.INNER, UnnestType.INNER) )
+      | ( <LEFT> ( <OUTER> )? correlateClause = JoinOrUnnestClause(JoinType.LEFTOUTER, UnnestType.LEFTOUTER) )
+      | ( <RIGHT> ( <OUTER> )? correlateClause = JoinClause(JoinType.RIGHTOUTER) )
+      | ( <CROSS> correlateClause = CrossJoinClause() )
+    )
+    {
+      correlateClauses.add(correlateClause);
+    }
+  )*
+  {
+    FromGraphClause fromGraphClause;
+    if (graphConstructor == null) {
+      fromGraphClause = new FromGraphClause(nameComponents.first, nameComponents.second,
+        matchClauses, correlateClauses);
+
+    } else {
+      fromGraphClause = new FromGraphClause(graphConstructor, matchClauses, correlateClauses);
+    }
+    return addSourceLocation(fromGraphClause, startToken);
+  }
+}
+
+@new
+List<PathPatternExpr> PathPatternExpressions() throws ParseException:
+{
+  List<PathPatternExpr> pathPatternExpressions = new ArrayList<PathPatternExpr>();
+  PathPatternExpr pathPattern = null;
+  VariableExpr variableExpr = null;
+}
+{
+  pathPattern = PathPatternExpression() { pathPatternExpressions.add(pathPattern); }
+  (
+    ( <AS> )? variableExpr = Variable()
+    {
+      int index = pathPatternExpressions.size() - 1;
+      pathPatternExpressions.get(index).setVariableExpr(variableExpr);
+    }
+  )?
+  ( LOOKAHEAD(2)
+    <COMMA> pathPattern = PathPatternExpression() { pathPatternExpressions.add(pathPattern); }
+    (
+      ( <AS> )? variableExpr = Variable()
+      {
+        int index = pathPatternExpressions.size() - 1;
+        pathPatternExpressions.get(index).setVariableExpr(variableExpr);
+      }
+    )?
+  )*
+  {
+    return pathPatternExpressions;
+  }
+}
+
+@new
+PathPatternExpr PathPatternExpression() throws ParseException:
+{
+  List<VertexPatternExpr> orderedVertexExpressions = new ArrayList<VertexPatternExpr>();
+  List<EdgePatternExpr> orderedEdgeExpressions = new ArrayList<EdgePatternExpr>();
+
+  Token startToken = null, edgeStartToken = null;
+  VertexPatternExpr vertexExpr = null;
+  EdgeDescriptor edgeDescriptor = null;
+}
+{
+  vertexExpr = VertexPatternExpression()
+  {
+    startToken = token;
+    orderedVertexExpressions.add(vertexExpr);
+  }
+  (
+    edgeDescriptor = EdgeDescriptor() { edgeStartToken = token; }
+    vertexExpr = VertexPatternExpression()
+    {
+      VertexPatternExpr leftVertex = orderedVertexExpressions.get(orderedVertexExpressions.size() - 1);
+      EdgePatternExpr edgePattern = new EdgePatternExpr(leftVertex, vertexExpr, edgeDescriptor);
+      orderedEdgeExpressions.add(addSourceLocation(edgePattern, edgeStartToken));
+      orderedVertexExpressions.add(vertexExpr);
+    }
+  )*
+  {
+    PathPatternExpr pathPattern = new PathPatternExpr(orderedVertexExpressions, orderedEdgeExpressions, null);
+    return addSourceLocation(pathPattern, startToken);
+  }
+}
+
+@new
+VertexPatternExpr VertexPatternExpression() throws ParseException:
+{
+  Set<ElementLabel> vertexLabels = new HashSet<ElementLabel>();
+  VariableExpr variableExpr = null;
+  Token startToken = null;
+  String vertexLabelName;
+}
+{
+  <LEFTPAREN> { startToken = token; }
+  (
+    variableExpr = Variable()
+  )?
+  (
+    <COLON> vertexLabelName = Identifier() { vertexLabels.add(new ElementLabel(vertexLabelName)); }
+    ( <BAR> vertexLabelName = Identifier() { vertexLabels.add(new ElementLabel(vertexLabelName)); } )*
+  )?
+  <RIGHTPAREN>
+  {
+    VertexPatternExpr vertexExpression = new VertexPatternExpr(variableExpr, vertexLabels);
+    return addSourceLocation(vertexExpression, startToken);
+  }
+}
+
+@new
+EdgeDescriptor EdgeDescriptor() throws ParseException:
+{
+  Pair<Set<ElementLabel>, Pair<Integer, Integer>> edgeDetail = null;
+  Token startToken = null;
+  VariableExpr edgeVariable = null;
+
+  // We default to undirected edges.
+  EdgeDescriptor.EdgeType edgeType = EdgeDescriptor.EdgeType.UNDIRECTED;
+}
+{
+  (
+    <MINUS> { startToken = token; }
+    (
+      <LEFTBRACKET>
+      (
+        edgeVariable = Variable()
+      )?
+      ( <COLON> edgeDetail = EdgeDetail() )?
+      <RIGHTBRACKET> <MINUS>
+    )?
+    ( <GT> { edgeType = EdgeDescriptor.EdgeType.LEFT_TO_RIGHT; } )?
+    |
+    <LT> {
+      startToken = token;
+      edgeType = EdgeDescriptor.EdgeType.RIGHT_TO_LEFT;
+    }
+    <MINUS>
+    (
+      <LEFTBRACKET>
+      (
+        edgeVariable = Variable()
+      )?
+      ( <COLON> edgeDetail = EdgeDetail() )?
+      <RIGHTBRACKET> <MINUS>
+    )?
+  )
+  {
+    // Edges (by default) are of class EDGE_PATTERN and are not sub-paths.
+    IGraphExpr.GraphExprKind edgeClass = IGraphExpr.GraphExprKind.EDGE_PATTERN;
+    Integer hopCountMin = 1;
+    Integer hopCountMax = 1;
+
+    Set<ElementLabel> labels = new HashSet<ElementLabel>();
+    if (edgeDetail != null) {
+      labels = edgeDetail.first;
+
+      // We have explicitly specified "{" and "}". Use sub-path semantics.
+      if (edgeDetail.second != null) {
+        edgeClass = IGraphExpr.GraphExprKind.PATH_PATTERN;
+        hopCountMin = edgeDetail.second.first;
+        hopCountMax = edgeDetail.second.second;
+      }
+    }
+
+    return new EdgeDescriptor(edgeType, edgeClass, labels, edgeVariable, hopCountMin, hopCountMax);
+  }
+}
+
+@new
+Pair<Set<ElementLabel>, Pair<Integer, Integer>> EdgeDetail() throws ParseException:
+{
+  Set<ElementLabel> edgeLabels = new HashSet<ElementLabel>();
+  Pair<Integer, Integer> repetitionQuantifier = null;
+  String labelName = null;
+}
+{
+  (
+    // Note: we want to forbid LABEL_1|LABEL_2{...}.
+    LOOKAHEAD(2, <BAR>)
+    (
+      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
+      <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
+      ( <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); } )*
+    )
+    |
+    (
+      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
+      |
+      <LEFTPAREN>
+      labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); }
+      ( <BAR> labelName = Identifier() { edgeLabels.add(new ElementLabel(labelName)); } )*
+      <RIGHTPAREN>
+    )
+    ( repetitionQuantifier = EdgeRepetitionQuantifier() )?
+    |
+    ( repetitionQuantifier = EdgeRepetitionQuantifier() )
+  )
+  {
+    return new Pair<Set<ElementLabel>, Pair<Integer, Integer>> (edgeLabels, repetitionQuantifier);
+  }
+}
+
+@new
+Pair<Integer, Integer> EdgeRepetitionQuantifier() throws ParseException:
+{
+  Integer hopCountMin = null;
+  Integer hopCountMax = null;
+}
+{
+  <LEFTBRACE>
+  ( // Note: we forbid unbounded edge repetition.
+    ( <INTEGER_LITERAL> { hopCountMin = Integer.valueOf(token.image); } )?
+    <COMMA> <INTEGER_LITERAL> { hopCountMax = Integer.valueOf(token.image); }
+  )
+  <RIGHTBRACE>
+  {
+    return new Pair<Integer, Integer>(hopCountMin, hopCountMax);
   }
 }
 
@@ -344,6 +689,7 @@
   | <GRAPH: "graph">
   | <SOURCE: "source">
   | <VERTEX: "vertex">
+  | <MATCH: "match">
 }
 
 @new_at_the_end
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 0820e29..9c83ed5 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
@@ -26,7 +26,7 @@
 import org.apache.asterix.test.runtime.ExecutionTestUtil;
 import org.apache.asterix.testframework.context.TestCaseContext;
 import org.apache.asterix.testframework.xml.TestGroup;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.hyracks.util.file.FileUtil;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -36,10 +36,9 @@
 
 @RunWith(Parameterized.class)
 public class GraphixExecutionTest {
-    protected static final String PATH_ACTUAL = "target/rttest" + File.separator;
-    protected static final String PATH_BASE =
-            StringUtils.join(new String[] { "src", "test", "resources", "runtimets" }, File.separator);
-    protected static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
+    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");
     private static final String TEST_SUITE_FILE = "testsuite.xml";
     private static final String ONLY_SUITE_FILE = "only.xml";
 
@@ -57,7 +56,7 @@
     public static void setUp() throws Exception {
         //noinspection ResultOfMethodCallIgnored
         new File(PATH_ACTUAL).mkdirs();
-        ExecutionTestUtil.setUp(true, TEST_CONFIG_FILE_NAME, integrationUtil, false, null);
+        ExecutionTestUtil.setUp(true, FILE_TEST_CONFIG, integrationUtil, false, null);
     }
 
     @AfterClass
@@ -66,7 +65,7 @@
         integrationUtil.removeTestStorageFiles();
     }
 
-    @Parameters(name = "ExecutionTest {index}: {0}")
+    @Parameters(name = "GraphixExecutionTest {index}: {0}")
     public static Collection<Object[]> tests() throws Exception {
         Collection<Object[]> test_cases = buildTestsInXml(ONLY_SUITE_FILE);
         if (test_cases.size() == 0) {
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 e3ea8e9..0bafd46 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
@@ -16,12 +16,14 @@
 
 import org.apache.asterix.api.common.AsterixHyracksIntegrationUtil;
 import org.apache.hyracks.test.support.TestUtils;
+import org.apache.hyracks.util.file.FileUtil;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 @RunWith(Parameterized.class)
 public class GraphixIntegrationUtil extends AsterixHyracksIntegrationUtil {
-    private static final String DEFAULT_CONF_FILE = "asterixdb/asterix-opt/asterix-graphix/src/main/resources/cc.conf";
+    private static final String FILE_DEFAULT_CONF =
+            FileUtil.joinPath("asterixdb", "asterix-opt", "asterix-graphix", "src", "main", "resources", "cc.conf");
 
     public static void main(String[] args) {
         TestUtils.redirectLoggingToConsole();
@@ -29,7 +31,7 @@
         try {
             boolean cleanupOnStart = Boolean.getBoolean("cleanup.start");
             boolean cleanupOnShutdown = Boolean.getBoolean("cleanup.shutdown");
-            String confFile = System.getProperty("external.lib", DEFAULT_CONF_FILE);
+            String confFile = System.getProperty("conf.path", FILE_DEFAULT_CONF);
             graphixIntegrationUtil.run(cleanupOnStart, cleanupOnShutdown, confFile);
         } catch (Exception e) {
             e.printStackTrace();
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 bf2c756..31d8a8e 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
@@ -22,7 +22,7 @@
 
 import org.apache.asterix.test.common.TestExecutor;
 import org.apache.asterix.testframework.context.TestCaseContext;
-import org.apache.commons.lang3.StringUtils;
+import org.apache.hyracks.util.file.FileUtil;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -32,10 +32,9 @@
 
 @RunWith(Parameterized.class)
 public class GraphixMetadataTest {
-    private static final String PATH_ACTUAL = "target" + File.separator + "mdtest" + File.separator;
-    private static final String PATH_BASE =
-            StringUtils.join(new String[] { "src", "test", "resources", "metadata" + File.separator }, File.separator);
-    private static final String TEST_CONFIG_FILE_NAME = "src/main/resources/cc.conf";
+    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 TestExecutor testExecutor = new TestExecutor();
     private static final GraphixIntegrationUtil integrationUtil = new GraphixIntegrationUtil();
@@ -50,7 +49,7 @@
     public static void setUp() throws Exception {
         //noinspection ResultOfMethodCallIgnored
         new File(PATH_ACTUAL).mkdirs();
-        integrationUtil.init(true, TEST_CONFIG_FILE_NAME);
+        integrationUtil.init(true, FILE_TEST_CONFIG);
     }
 
     @AfterClass
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
new file mode 100644
index 0000000..d9e8ead
--- /dev/null
+++ b/asterix-graphix/src/test/java/org/apache/asterix/graphix/test/SqlppExecutionTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.apache.asterix.test.common.TestExecutor;
+import org.apache.asterix.test.runtime.ExecutionTestUtil;
+import org.apache.asterix.testframework.context.TestCaseContext;
+import org.apache.asterix.testframework.xml.TestGroup;
+import org.apache.hyracks.util.file.FileUtil;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SqlppExecutionTest {
+    protected static final String PATH_ACTUAL = FileUtil.joinPath("target", "srttest");
+    protected static final String PATH_BASE = FileUtil.joinPath("src", "test", "resources", "runtimets");
+    private static final String TEST_SUITE_FILE = "testsuite_sqlpp.xml";
+    private static final String ONLY_SUITE_FILE = "only_sqlpp.xml";
+
+    // 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");
+
+    private static final GraphixIntegrationUtil integrationUtil = new GraphixIntegrationUtil();
+    private static final TestExecutor testExecutor = new TestExecutor();
+
+    protected static TestGroup FailedGroup;
+    protected TestCaseContext tcCtx;
+
+    public SqlppExecutionTest(TestCaseContext tcCtx) {
+        this.tcCtx = tcCtx;
+    }
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        //noinspection ResultOfMethodCallIgnored
+        new File(PATH_ACTUAL).mkdirs();
+        ExecutionTestUtil.setUp(true, FILE_TEST_CONFIG, integrationUtil, false, null);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        ExecutionTestUtil.tearDown(true, integrationUtil, true);
+        integrationUtil.removeTestStorageFiles();
+    }
+
+    @Parameters(name = "SqlppExecutionTest {index}: {0}")
+    public static Collection<Object[]> tests() throws Exception {
+        // TODO (GLENN): Find out the SQLPP tests we can run here.
+        // Collection<Object[]> test_cases = buildTestsInXml(ONLY_SUITE_FILE);
+        // if (test_cases.size() == 0) {
+        //    test_cases = buildTestsInXml(TEST_SUITE_FILE);
+        // }
+        // return test_cases;
+        return Collections.emptyList();
+    }
+
+    protected static Collection<Object[]> buildTestsInXml(String xmlfile) throws Exception {
+        Collection<Object[]> testArgs = new ArrayList<>();
+        TestCaseContext.Builder b = new TestCaseContext.Builder();
+        for (TestCaseContext ctx : b.build(new File(PATH_BASE), xmlfile)) {
+            testArgs.add(new Object[] { ctx });
+        }
+        return testArgs;
+    }
+
+    @Test
+    public void test() throws Exception {
+        testExecutor.executeTest(PATH_ACTUAL, tcCtx, null, false, FailedGroup);
+    }
+}
diff --git a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.1.adm b/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.1.adm
deleted file mode 100644
index 31c330c..0000000
--- a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.1.adm
+++ /dev/null
@@ -1,2 +0,0 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_1", "Dependencies": [ [ [ "Yelp_A", "Reviews" ], [ "Yelp_B", "Users" ], [ "Yelp_B", "Friends" ] ], [ [ "Yelp_B", "Yelpers" ] ], [ [ "Yelp_A", "RelevantBusinesses", "0" ] ] ], "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Definitions": [ "Yelp_B.Yelpers" ] }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Definitions": [ "( FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R )" ] }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Definitions": [ "( FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B )" ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "PrimaryKey": [ [ "user_id" ], [ "friend" ] ], "DestinationKey": [ [ "friend" ] ], "SourceKey": [ [ "user_id" ] ], "Definitions": [ "( FROM    Yelp_B.Users U\n                       UNNEST  U.friends F\n                       SELECT  U.user_id, F AS friend )", "Yelp_B.Friends" ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] } ] }
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Dependencies": [ [ [ "Yelp_A", "Reviews" ] ], [  ], [ [ "Yelp_A", "RelevantBusinesses", "0" ] ] ], "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Definitions": [ "( FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R )" ] }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Definitions": [ "( FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B )" ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] } ] }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.2.adm b/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.2.adm
deleted file mode 100644
index a749b72..0000000
--- a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.2.adm
+++ /dev/null
@@ -1 +0,0 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Dependencies": [ [ [ "Yelp_A", "Reviews" ] ], [  ], [ [ "Yelp_A", "RelevantBusinesses", "0" ] ] ], "Vertices": [ { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Definitions": [ "( FROM    Yelp_A.Reviews R\n                       SELECT  VALUE R )" ] }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Definitions": [ "( FROM    Yelp_A.RelevantBusinesses() B\n                       SELECT  VALUE B )" ] } ], "Edges": [ { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] } ] }
diff --git a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.3.adm b/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.3.adm
deleted file mode 100644
index 80af6cb..0000000
--- a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.3.adm
+++ /dev/null
@@ -1 +0,0 @@
-{ "DataverseName": "Yelp", "GraphName": "YelpGraph_2", "Dependencies": [ [ [ "Yelp_A", "Reviews" ], [ "Yelp_B", "Users" ], [ "Yelp_B", "Friends" ] ], [ [ "Yelp_B", "Yelpers" ] ], [ [ "Yelp_A", "RelevantBusinesses", "0" ] ] ], "Vertices": [ { "Label": "User", "PrimaryKey": [ [ "user_id" ] ], "Definitions": [ "Yelp_B.Yelpers" ] }, { "Label": "Review", "PrimaryKey": [ [ "review_id" ] ], "Definitions": [ "( FROM    Yelp_A.Reviews R\n                               SELECT  VALUE R )" ] }, { "Label": "Business", "PrimaryKey": [ [ "business_id" ] ], "Definitions": [ "( FROM    Yelp_A.RelevantBusinesses() B\n                               SELECT  VALUE B )" ] } ], "Edges": [ { "Label": "FRIENDS_WITH", "DestinationLabel": "User", "SourceLabel": "User", "PrimaryKey": [ [ "user_id" ], [ "friend" ] ], "DestinationKey": [ [ "friend" ] ], "SourceKey": [ [ "user_id" ] ], "Definitions": [ "( FROM    Yelp_B.Users U\n                               UNNEST  U.friends F\n                               SELECT  U.user_id, F AS friend )", "Yelp_B.Friends" ] }, { "Label": "MADE_BY", "DestinationLabel": "User", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "user_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] }, { "Label": "ABOUT", "DestinationLabel": "Business", "SourceLabel": "Review", "PrimaryKey": [ [ "review_id" ] ], "DestinationKey": [ [ "business_id" ] ], "SourceKey": [ [ "review_id" ] ], "Definitions": [ "" ] } ] }
diff --git a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.4.adm b/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.4.adm
deleted file mode 100644
index c1a0ea2..0000000
--- a/asterix-graphix/src/test/resources/metadata/results/graphix/yelp-example/yelp-example.4.adm
+++ /dev/null
@@ -1 +0,0 @@
-{ "count": 0 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.1.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
similarity index 78%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.1.ddl.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
index 0f4761d..db8589e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.1.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.1.ddl.sqlpp
@@ -40,9 +40,9 @@
 CREATE DATASET    Users (GenericType)
 PRIMARY KEY       _id AUTOGENERATED;
 CREATE FUNCTION   RelevantBusinesses()
-                  { FROM Businesses B
-                    WHERE B.stars > 3.5
-                    SELECT B.* };
+                  { FROM    Businesses B
+                    WHERE   B.stars > 3.5
+                    SELECT  B.* };
 
 USE               Yelp_B;
 CREATE TYPE       GenericType
@@ -75,21 +75,26 @@
                   AS ( FROM    Yelp_A.RelevantBusinesses() B
                        SELECT  VALUE B ),
 EDGE              (:User)-[:FRIENDS_WITH]->(:User)
-                  PRIMARY KEY (user_id, friend)
                   SOURCE KEY (user_id)
                   DESTINATION KEY (friend)
                   AS ( FROM    Yelp_B.Users U
                        UNNEST  U.friends F
                        SELECT  U.user_id, F AS friend ),
 EDGE              (:User)-[:FRIENDS_WITH]->(:User)
-                          PRIMARY KEY (user_id, friend)
-                          SOURCE KEY (user_id)
-                          DESTINATION KEY (friend)
-                          AS Yelp_B.Friends,
+                  SOURCE KEY (user_id)
+                  DESTINATION KEY (friend)
+                  AS ( FROM    Yelp_B.Friends F
+                       SELECT  F.* ),
 EDGE              (:Review)-[:MADE_BY]->(:User)
-                  DESTINATION KEY (user_id),
+                  SOURCE KEY (review_id)
+                  DESTINATION KEY (user_id)
+                  AS ( FROM    Yelp_A.Reviews R
+                       SELECT  R.review_id, R.user_id ),
 EDGE              (:Review)-[:ABOUT]->(:Business)
-                  DESTINATION KEY (business_id);
+                  SOURCE KEY (review_id)
+                  DESTINATION KEY (business_id)
+                  AS ( FROM    Yelp_A.Reviews R
+                       SELECT  R.review_id, R.business_id );
 
 CREATE GRAPH      YelpGraph_2 IF NOT EXISTS AS
 VERTEX            (:Review)
@@ -101,7 +106,10 @@
                   AS ( FROM    Yelp_A.RelevantBusinesses() B
                        SELECT  VALUE B ),
 EDGE              (:Review)-[:ABOUT]->(:Business)
-                  DESTINATION KEY (business_id);
+                  SOURCE KEY (review_id)
+                  DESTINATION KEY (business_id)
+                  AS ( FROM    Yelp_A.Reviews R
+                       SELECT  VALUE R );
 CREATE GRAPH      YelpGraph_2 IF NOT EXISTS AS
 VERTEX            (:Review)
                   PRIMARY KEY (review_id)
@@ -112,4 +120,7 @@
                   AS ( FROM    Yelp_A.RelevantBusinesses() B
                        SELECT  VALUE B ),
 EDGE              (:Review)-[:ABOUT]->(:Business)
-                  DESTINATION KEY (business_id);
+                  SOURCE KEY (review_id)
+                  DESTINATION KEY (business_id)
+                  AS ( FROM    Yelp_A.Reviews R
+                       SELECT  VALUE R );
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.2.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.2.query.sqlpp
similarity index 64%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.2.query.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.2.query.sqlpp
index b8151af..ca71c1e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.2.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.2.query.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-FROM      `Metadata`.`Graph` G
-SELECT    G.DataverseName, G.GraphName, G.Dependencies, G.Vertices, G.Edges
-ORDER BY  G.DataverseName, G.GraphName;
+FROM      `Metadata`.`Graph` G,
+          `Metadata`.`GraphDependency` GD,
+          GD.Dependencies D
+WHERE     G.DataverseName = GD.DataverseName AND
+          G.GraphName = GD.EntityName AND
+          GD.Kind = "GRAPH"
+SELECT    G.DataverseName, G.GraphName, G.Vertices, G.Edges,
+          D.DataverseName AS DependentDataverse,
+          D.EntityName AS DependentName,
+          D.Kind AS DependentKind
+ORDER BY  G.DataverseName, G.GraphName, D;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.3.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.3.ddl.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.3.ddl.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.3.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.4.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.4.query.sqlpp
similarity index 64%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.4.query.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.4.query.sqlpp
index b8151af..ca71c1e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.4.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.4.query.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-FROM      `Metadata`.`Graph` G
-SELECT    G.DataverseName, G.GraphName, G.Dependencies, G.Vertices, G.Edges
-ORDER BY  G.DataverseName, G.GraphName;
+FROM      `Metadata`.`Graph` G,
+          `Metadata`.`GraphDependency` GD,
+          GD.Dependencies D
+WHERE     G.DataverseName = GD.DataverseName AND
+          G.GraphName = GD.EntityName AND
+          GD.Kind = "GRAPH"
+SELECT    G.DataverseName, G.GraphName, G.Vertices, G.Edges,
+          D.DataverseName AS DependentDataverse,
+          D.EntityName AS DependentName,
+          D.Kind AS DependentKind
+ORDER BY  G.DataverseName, G.GraphName, D;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.5.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
similarity index 74%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.5.ddl.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
index 586a718..0835454 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.5.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.5.ddl.sqlpp
@@ -30,18 +30,23 @@
                           AS ( FROM    Yelp_A.RelevantBusinesses() B
                                SELECT  VALUE B ),
 EDGE                      (:User)-[:FRIENDS_WITH]->(:User)
-                          PRIMARY KEY (user_id, friend)
                           SOURCE KEY (user_id)
                           DESTINATION KEY (friend)
                           AS ( FROM    Yelp_B.Users U
                                UNNEST  U.friends F
                                SELECT  U.user_id, F AS friend ),
 EDGE                      (:User)-[:FRIENDS_WITH]->(:User)
-                                  PRIMARY KEY (user_id, friend)
-                                  SOURCE KEY (user_id)
-                                  DESTINATION KEY (friend)
-                                  AS Yelp_B.Friends,
+                          SOURCE KEY (user_id)
+                          DESTINATION KEY (friend)
+                          AS ( FROM    Yelp_B.Friends F
+                               SELECT  F.* ),
 EDGE                      (:Review)-[:MADE_BY]->(:User)
-                          DESTINATION KEY (user_id),
+                          SOURCE KEY (review_id)
+                          DESTINATION KEY (user_id)
+                          AS ( FROM    Yelp_A.Reviews R
+                               SELECT  VALUE R ),
 EDGE                      (:Review)-[:ABOUT]->(:Business)
-                          DESTINATION KEY (business_id);
+                          SOURCE KEY (review_id)
+                          DESTINATION KEY (business_id)
+                          AS ( FROM    Yelp_A.Reviews R
+                               SELECT  VALUE R );
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.6.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.6.query.sqlpp
similarity index 64%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.6.query.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.6.query.sqlpp
index b8151af..ca71c1e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.6.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.6.query.sqlpp
@@ -17,6 +17,14 @@
  * under the License.
  */
 
-FROM      `Metadata`.`Graph` G
-SELECT    G.DataverseName, G.GraphName, G.Dependencies, G.Vertices, G.Edges
-ORDER BY  G.DataverseName, G.GraphName;
+FROM      `Metadata`.`Graph` G,
+          `Metadata`.`GraphDependency` GD,
+          GD.Dependencies D
+WHERE     G.DataverseName = GD.DataverseName AND
+          G.GraphName = GD.EntityName AND
+          GD.Kind = "GRAPH"
+SELECT    G.DataverseName, G.GraphName, G.Vertices, G.Edges,
+          D.DataverseName AS DependentDataverse,
+          D.EntityName AS DependentName,
+          D.Kind AS DependentKind
+ORDER BY  G.DataverseName, G.GraphName, D;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.7.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.7.ddl.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.7.ddl.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.7.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.8.query.sqlpp
similarity index 68%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.8.query.sqlpp
index 9667fd6..c238c7c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/create-drop-graph/create-drop-graph.8.query.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+SELECT VALUE {
+    "graphCount": (
+        FROM    `Metadata`.`Graph` G
+        WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
+        SELECT  VALUE COUNT(*)
+    )[0],
+    "dependencyCount": (
+        FROM    `Metadata`.`GraphDependency` GD
+        WHERE   GD.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
+        SELECT  VALUE COUNT(*)
+    )[0]
+};
diff --git a/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.1.ddl.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.1.ddl.sqlpp
new file mode 100644
index 0000000..127319a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.1.ddl.sqlpp
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       GenericType
+AS                { _id: uuid };
+CREATE DATASET    Users (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
+CREATE DATASET    Reviews (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
+
+CREATE GRAPH      YelpGraph AS
+VERTEX            (:User)
+                  PRIMARY KEY (user_id)
+                  AS Users,
+VERTEX            (:Review)
+                  PRIMARY KEY (review_id)
+                  AS 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 );
+
+// Create a function, view, and another graph that depend on the graph above.
+CREATE FUNCTION   YelpGraphFunction ()
+                  { FROM GRAPH  YelpGraph
+                    MATCH       (u)-[]->(r)
+                    SELECT      u, r
+                    LIMIT       1 };
+CREATE VIEW       YelpGraphView AS
+                  FROM GRAPH  YelpGraph
+                  MATCH       (u:User)<-[:MADE_BY]-(:Review)
+                  SELECT      u.user_id
+                  LIMIT       1;
+CREATE GRAPH      YelpGraphGraph AS
+VERTEX            (:UsersWithReviews)
+                  PRIMARY KEY (user_id)
+                  AS ( FROM GRAPH  YelpGraph
+                       MATCH       (u:User)<-[:MADE_BY]-(:Review)
+                       SELECT      u ),
+EDGE              (:UsersWithReviews)-[:FRIENDS_WITH]->(:UsersWithReviews)
+                  SOURCE KEY (user_id)
+                  DESTINATION KEY (best_friend)
+                  AS ( FROM GRAPH  YelpGraph
+                       MATCH       (u:User)<-[:MADE_BY]-(Review)
+                       SELECT      u.user_id, u.best_friend );
diff --git a/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.2.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.2.query.sqlpp
new file mode 100644
index 0000000..cef0add
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.2.query.sqlpp
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+SELECT    VALUE {
+    "graph": (
+        FROM      `Metadata`.`Graph` G,
+                  `Metadata`.`GraphDependency` GD,
+                  GD.Dependencies D
+        WHERE     G.DataverseName = GD.DataverseName AND
+                  G.GraphName = GD.EntityName AND
+                  GD.DataverseName = "Yelp" AND
+                  GD.Kind = "GRAPH"
+        SELECT    G.DataverseName, G.GraphName,
+                  D.DataverseName AS DependentDataverse,
+                  D.EntityName AS DependentName,
+                  D.Kind AS DependentKind
+        ORDER BY  G.DataverseName, G.GraphName, D
+    ),
+    "function": (
+        FROM      `Metadata`.`Function` F,
+                  `Metadata`.`GraphDependency` GD,
+                  GD.Dependencies D
+        WHERE     F.DataverseName = GD.DataverseName AND
+                  F.Name = GD.EntityName AND
+                  F.Arity = GD.EntityDetail AND
+                  GD.DataverseName = "Yelp" AND
+                  GD.Kind = "FUNCTION"
+        SELECT    F.DataverseName, F.Name, F.Arity,
+                  D.DataverseName AS DependentDataverse,
+                  D.EntityName AS DependentName,
+                  D.Kind AS DependentKind
+        ORDER BY  F.DataverseName, F.Name, D
+    ),
+    "view": (
+        FROM      `Metadata`.`Dataset` DD,
+                  `Metadata`.`GraphDependency` GD,
+                  GD.Dependencies D
+        WHERE     DD.DataverseName = GD.DataverseName AND
+                  DD.DatasetName = GD.EntityName AND
+                  DD.DatasetType = "VIEW" AND
+                  GD.DataverseName = "Yelp" AND
+                  GD.Kind = "VIEW"
+        SELECT    DD.DataverseName, DD.DatasetName,
+                  D.DataverseName AS DependentDataverse,
+                  D.EntityName AS DependentName,
+                  D.Kind AS DependentKind
+        ORDER BY  DD.DataverseName, DD.DatasetName, D
+    )
+};
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.3.ddl.sqlpp
similarity index 86%
rename from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
rename to asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.3.ddl.sqlpp
index 9667fd6..df554f4 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.3.ddl.sqlpp
@@ -17,6 +17,4 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE Yelp;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.4.query.sqlpp
similarity index 86%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.4.query.sqlpp
index 9667fd6..bbb0b64 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/metadatats/queries/graphix/on-graph-dependency/on-graph-dependency.4.query.sqlpp
@@ -17,6 +17,6 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+FROM    `Metadata`.`GraphDependency` GD
+WHERE   GD.DataverseName = "Yelp"
+SELECT  VALUE COUNT(*);
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
new file mode 100644
index 0000000..2e8cfe1
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.2.adm
@@ -0,0 +1,7 @@
+{ "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
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
new file mode 100644
index 0000000..2f07e94
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.4.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..f761b45
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.6.adm
@@ -0,0 +1,5 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.8.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.8.adm
new file mode 100644
index 0000000..fa28578
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/create-drop-graph/create-drop-graph.8.adm
@@ -0,0 +1 @@
+{ "graphCount": 0, "dependencyCount": 0 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.2.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.2.adm
new file mode 100644
index 0000000..b5547c5
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.2.adm
@@ -0,0 +1 @@
+{ "graph": [ { "DataverseName": "Yelp", "GraphName": "YelpGraph", "DependentDataverse": "Yelp", "DependentName": "Reviews", "DependentKind": "DATASET" }, { "DataverseName": "Yelp", "GraphName": "YelpGraph", "DependentDataverse": "Yelp", "DependentName": "Users", "DependentKind": "DATASET" }, { "DataverseName": "Yelp", "GraphName": "YelpGraphGraph", "DependentDataverse": "Yelp", "DependentName": "Reviews", "DependentKind": "DATASET" }, { "DataverseName": "Yelp", "GraphName": "YelpGraphGraph", "DependentDataverse": "Yelp", "DependentName": "Users", "DependentKind": "DATASET" }, { "DataverseName": "Yelp", "GraphName": "YelpGraphGraph", "DependentDataverse": "Yelp", "DependentName": "YelpGraph", "DependentKind": "GRAPH" } ], "function": [ { "DataverseName": "Yelp", "Name": "YelpGraphFunction", "Arity": "0", "DependentDataverse": "Yelp", "DependentName": "YelpGraph", "DependentKind": "GRAPH" } ], "view": [ { "DataverseName": "Yelp", "DatasetName": "YelpGraphView", "DependentDataverse": "Yelp", "DependentName": "YelpGraph", "DependentKind": "GRAPH" } ] }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.4.adm b/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.4.adm
new file mode 100644
index 0000000..c227083
--- /dev/null
+++ b/asterix-graphix/src/test/resources/metadatats/results/graphix/on-graph-dependency/on-graph-dependency.4.adm
@@ -0,0 +1 @@
+0
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/testsuite.xml b/asterix-graphix/src/test/resources/metadatats/testsuite.xml
similarity index 72%
rename from asterix-graphix/src/test/resources/metadata/testsuite.xml
rename to asterix-graphix/src/test/resources/metadatats/testsuite.xml
index 2f45b91..9861530 100644
--- a/asterix-graphix/src/test/resources/metadata/testsuite.xml
+++ b/asterix-graphix/src/test/resources/metadatats/testsuite.xml
@@ -18,10 +18,17 @@
             ResultOffsetPath="results"
             QueryOffsetPath="queries"
             QueryFileExtension=".sqlpp">
-  <test-group name="yelp-example">
+  <test-group name="create-drop-graph">
     <test-case FilePath="graphix">
-      <compilation-unit name="yelp-example">
-        <output-dir compare="Text">yelp-example</output-dir>
+      <compilation-unit name="create-drop-graph">
+        <output-dir compare="Text">create-drop-graph</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="on-graph-dependency">
+    <test-case FilePath="graphix">
+      <compilation-unit name="on-graph-dependency">
+        <output-dir compare="Text">on-graph-dependency</output-dir>
       </compilation-unit>
     </test-case>
   </test-group>
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.1.ddl.sqlpp
similarity index 88%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.1.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.1.ddl.sqlpp
index ff47a77..d74de05 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.1.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.1.ddl.sqlpp
@@ -37,6 +37,9 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 DROP DATASET      GenericDataset;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.10.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.10.ddl.sqlpp
similarity index 85%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.10.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.10.ddl.sqlpp
index fe00035..26b75b2 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.10.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.10.ddl.sqlpp
@@ -39,7 +39,10 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 USE               TestDataverse2;
 CREATE TYPE       GenericType
@@ -56,7 +59,10 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 USE               TestDataverse1;
 CREATE GRAPH      TestGraph IF NOT EXISTS AS
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.11.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.11.ddl.sqlpp
similarity index 88%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.11.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.11.ddl.sqlpp
index ff0f01e..77706fa 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.11.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.11.ddl.sqlpp
@@ -37,7 +37,10 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 DROP GRAPH        GraphThatDoesntExist1 IF EXISTS;
 DROP GRAPH        GraphThatDoesntExist2;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.12.ddl.sqlpp
similarity index 80%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.12.ddl.sqlpp
index 5191339..2ec83fd 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.12.ddl.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Verify that a subquery as an element definition is a valid query.
+// Verify that we cannot drop a graph that a view is dependent on.
 
 DROP DATAVERSE    TestDataverse IF EXISTS;
 CREATE DATAVERSE  TestDataverse;
@@ -25,7 +25,6 @@
 
 CREATE TYPE       GenericType
 AS                { _id: uuid };
-
 CREATE DATASET    GenericDataset (GenericType)
 PRIMARY KEY       _id AUTOGENERATED;
 
@@ -37,9 +36,14 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (_id, _foreign_id)
                   SOURCE KEY (_id)
                   DESTINATION KEY (_foreign_id)
-                  AS ( FROM GenericDataset G,
-                            GenericDataset G2
-                       SELECT V );
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
+
+CREATE VIEW       TestView AS
+                  FROM GRAPH  TestGraph
+                  MATCH       (n)
+                  SELECT      n;
+
+DROP GRAPH        TestGraph;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.13.ddl.sqlpp
similarity index 78%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.13.ddl.sqlpp
index 5191339..a38fd4b 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.13.ddl.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Verify that a subquery as an element definition is a valid query.
+// Verify that we cannot drop a graph that a function is dependent on.
 
 DROP DATAVERSE    TestDataverse IF EXISTS;
 CREATE DATAVERSE  TestDataverse;
@@ -25,7 +25,6 @@
 
 CREATE TYPE       GenericType
 AS                { _id: uuid };
-
 CREATE DATASET    GenericDataset (GenericType)
 PRIMARY KEY       _id AUTOGENERATED;
 
@@ -37,9 +36,15 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (_id, _foreign_id)
                   SOURCE KEY (_id)
                   DESTINATION KEY (_foreign_id)
-                  AS ( FROM GenericDataset G,
-                            GenericDataset G2
-                       SELECT V );
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
+
+CREATE FUNCTION   TestFunction() {
+                      FROM GRAPH  TestGraph
+                      MATCH       (n)
+                      SELECT      n
+                  };
+
+DROP GRAPH        TestGraph;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.14.ddl.sqlpp
similarity index 64%
copy from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.14.ddl.sqlpp
index 5191339..ac9b1f6 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.14.ddl.sqlpp
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-// Verify that a subquery as an element definition is a valid query.
+// Verify that we cannot drop a graph that another graph is dependent on.
 
 DROP DATAVERSE    TestDataverse IF EXISTS;
 CREATE DATAVERSE  TestDataverse;
@@ -25,7 +25,6 @@
 
 CREATE TYPE       GenericType
 AS                { _id: uuid };
-
 CREATE DATASET    GenericDataset (GenericType)
 PRIMARY KEY       _id AUTOGENERATED;
 
@@ -37,9 +36,24 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (_id, _foreign_id)
                   SOURCE KEY (_id)
                   DESTINATION KEY (_foreign_id)
-                  AS ( FROM GenericDataset G,
-                            GenericDataset G2
-                       SELECT V );
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
+
+CREATE GRAPH      TestGraph2 AS
+VERTEX            (:Vertex1)
+                  PRIMARY KEY (_id)
+                  AS GenericDataset,
+VERTEX            (:Vertex2)
+                  PRIMARY KEY (_id)
+                  AS ( FROM GRAPH  TestGraph
+                       MATCH       (n)
+                       SELECT      n ),
+EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
+
+DROP GRAPH        TestGraph;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.2.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.2.ddl.sqlpp
similarity index 97%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.2.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.2.ddl.sqlpp
index 09693af..c51981c 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.2.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.2.ddl.sqlpp
@@ -39,7 +39,6 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (a, b)
                   SOURCE KEY (a)
                   DESTINATION KEY (b)
                   AS ( FROM TestFunction() T
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.3.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.3.ddl.sqlpp
similarity index 97%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.3.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.3.ddl.sqlpp
index a812ea1..533adfd 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.3.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.3.ddl.sqlpp
@@ -39,7 +39,6 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (a, b)
                   SOURCE KEY (a)
                   DESTINATION KEY (b)
                   AS ( FROM TestView T
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.4.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.4.ddl.sqlpp
similarity index 88%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.4.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.4.ddl.sqlpp
index 0cdec73..2360211 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.4.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.4.ddl.sqlpp
@@ -39,6 +39,9 @@
                   PRIMARY KEY (_id)
                   AS DatasetSynonym,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 DROP SYNONYM      DatasetSynonym;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.5.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.5.ddl.sqlpp
similarity index 90%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.5.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.5.ddl.sqlpp
index 844389f..bcd6df1 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.5.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.5.ddl.sqlpp
@@ -49,6 +49,9 @@
                   PRIMARY KEY (_id)
                   AS TestDataverse2.GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
 
 DROP DATAVERSE    TestDataverse2;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.6.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.6.ddl.sqlpp
similarity index 88%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.6.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.6.ddl.sqlpp
index f6173aa..2f29d61 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.6.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.6.ddl.sqlpp
@@ -37,4 +37,7 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.7.ddl.sqlpp
similarity index 96%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.7.ddl.sqlpp
index 5191339..3dce9df 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.7.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.7.ddl.sqlpp
@@ -37,7 +37,6 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex2)
-                  PRIMARY KEY (_id, _foreign_id)
                   SOURCE KEY (_id)
                   DESTINATION KEY (_foreign_id)
                   AS ( FROM GenericDataset G,
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.8.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp
similarity index 100%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.8.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.8.ddl.sqlpp
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.9.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.9.ddl.sqlpp
similarity index 87%
rename from asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.9.ddl.sqlpp
rename to asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.9.ddl.sqlpp
index 7c5269e..204900f 100644
--- a/asterix-graphix/src/test/resources/runtimets/queries/graphix/error-handling/error-handling.9.ddl.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/create-drop-error/create-drop-error.9.ddl.sqlpp
@@ -37,4 +37,7 @@
                   PRIMARY KEY (_id)
                   AS GenericDataset,
 EDGE              (:Vertex1)-[:EDGE_1]->(:Vertex3)
-                  DESTINATION KEY (_foreign_id);
+                  SOURCE KEY (_id)
+                  DESTINATION KEY (_foreign_id)
+                  AS ( FROM   GenericDataset GD
+                       SELECT GD._id, GD._foreign_id );
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.1.ddl.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.1.ddl.sqlpp
new file mode 100644
index 0000000..492a7aa
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.1.ddl.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       GenericType
+AS                { _id: uuid };
+CREATE DATASET    Reviews (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
+CREATE DATASET    Users (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
+
+CREATE GRAPH      YelpGraph AS
+VERTEX            (:User)
+                  PRIMARY KEY (user_id)
+                  AS Users,
+VERTEX            (:Review)
+                  PRIMARY KEY (review_id)
+                  AS Reviews,
+EDGE              (:User)-[:FRIENDS_WITH]->(:User)
+                  SOURCE KEY (user_id)
+                  DESTINATION KEY (friend)
+                  AS ( FROM    Users U
+                       UNNEST  U.friends F
+                       SELECT  U.user_id AS user_id,
+                               F AS friend ),
+EDGE              (:Review)-[:MADE_BY]->(:User)
+                  SOURCE KEY (review_id)
+                  DESTINATION KEY (user_id)
+                  AS ( FROM    Reviews R
+                       SELECT  R.review_id, R.user_id );
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.2.update.sqlpp
similarity index 62%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.2.update.sqlpp
index 9667fd6..fa0d18d 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.2.update.sqlpp
@@ -17,6 +17,20 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1, "friends": [2, 3], "best_friend": 2 },
+  { "user_id": 2, "friends": [1] },
+  { "user_id": 3, "friends": [1], "best_friend": 1 },
+  { "user_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 }
+];
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.3.query.sqlpp
similarity index 84%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.3.query.sqlpp
index 9667fd6..2e67ca8 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.3.query.sqlpp
@@ -17,6 +17,8 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// There is one dangling vertex, and zero edges.
+FROM GRAPH  Yelp.YelpGraph
+MATCH       (u:User)
+SELECT      u.user_id
+ORDER BY    u.user_id;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.4.query.sqlpp
similarity index 75%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.4.query.sqlpp
index 9667fd6..fc8e61c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.4.query.sqlpp
@@ -17,6 +17,10 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// There are two dangling vertices of the same label, and zero edges.
+FROM GRAPH  Yelp.YelpGraph
+MATCH       (u1:User), (u2:User)
+SELECT      u1.user_id AS u1_user_id,
+            u2.user_id AS u2_user_id
+ORDER BY    u1_user_id,
+            u2_user_id;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
similarity index 77%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
index 9667fd6..b37cb34 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.5.query.sqlpp
@@ -17,6 +17,10 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// There are two dangling vertices of different labels, and zero edges.
+FROM GRAPH  Yelp.YelpGraph
+MATCH       (u:User), (r:Review)
+SELECT      u.user_id,
+            r.review_id
+ORDER BY    u.user_id,
+            r.review_id;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.6.query.sqlpp
similarity index 75%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.6.query.sqlpp
index 9667fd6..f41dcdf 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/dangling-vertices/dangling-vertices.6.query.sqlpp
@@ -17,6 +17,10 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// There are two dangling vertices, but each vertex is logically connected to an edge.
+FROM GRAPH  Yelp.YelpGraph
+MATCH       (u:User)<-[:MADE_BY]-(r:Review), (u), (r)
+SELECT      u.user_id,
+            r.review_id
+ORDER BY    u.user_id,
+            r.review_id;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.1.ddl.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.1.ddl.sqlpp
index 9667fd6..ad55e77 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.1.ddl.sqlpp
@@ -17,6 +17,13 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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    Users (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.2.update.sqlpp
similarity index 68%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.2.update.sqlpp
index 9667fd6..5780dc6 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.2.update.sqlpp
@@ -17,6 +17,21 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 },
+  { "user_id": 5 },
+  { "user_id": 6 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 3, "friend": 4 },
+  { "user_id": 4, "friend": 5 },
+  { "user_id": 5, "friend": 6 }
+];
\ No newline at end of file
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
new file mode 100644
index 0000000..ee62ea8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.3.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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 directed edge definition of exactly two hops.
+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.Friends F
+                        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)
+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
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
new file mode 100644
index 0000000..bfef02f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.4.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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 directed edge definition of exactly three hops.
+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.Friends F
+                        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)
+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
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
new file mode 100644
index 0000000..e12bf13
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.5.query.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+// Two directed edge definitions of exactly two hops.
+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.Friends F
+                        SELECT  F.user_id AS user_id,
+                                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
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
new file mode 100644
index 0000000..ec08830
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.6.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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 directed edge definition of exactly one hop (this should be expanded as well).
+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.Friends F
+                        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
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.7.update.sqlpp
similarity index 85%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.7.update.sqlpp
index 9667fd6..44e6529 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.7.update.sqlpp
@@ -17,6 +17,9 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+// We insert a backwards-edge here as well.
+INSERT INTO       Friends [
+  { "user_id": 2, "friend": 1 }
+];
\ 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
new file mode 100644
index 0000000..fcedada
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/fixed-sub-path/fixed-sub-path.8.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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 undirected edge definition of exactly two hops.
+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.Friends F
+                        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)
+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
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.1.ddl.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.1.ddl.sqlpp
index 9667fd6..1932ddc 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.1.ddl.sqlpp
@@ -17,6 +17,13 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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    Users (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.2.update.sqlpp
similarity index 74%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.2.update.sqlpp
index 9667fd6..864e842 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.2.update.sqlpp
@@ -17,6 +17,17 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 2, "friend": 1 }
+];
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.3.query.sqlpp
similarity index 76%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.3.query.sqlpp
index 9667fd6..ea3090e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.3.query.sqlpp
@@ -17,6 +17,11 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// (u) should never be equal to (v).
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users
+MATCH       (u), (v)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id
+ORDER BY    u, v;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.4.query.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.4.query.sqlpp
index 9667fd6..b3ee92f 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.4.query.sqlpp
@@ -17,6 +17,12 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// (u), (v), and (w) should never be equal.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users
+MATCH       (u), (v), (w)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id
+ORDER BY    u, v, w;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.5.query.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.5.query.sqlpp
index 9667fd6..974fd77 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.5.query.sqlpp
@@ -17,6 +17,17 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// (u), (v), and (w) should never be equal.
+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.Friends F
+                        SELECT F.user_id, F.friend )
+MATCH       (u)->(v)->(w)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id
+ORDER BY    u, v, w;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
index 9667fd6..e3f1e7c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.6.query.sqlpp
@@ -17,6 +17,12 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// There should be results where (u) = (v).
+SET  `graphix.match-evaluation` "homomorphism";
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users
+MATCH       (u), (v)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id
+ORDER BY    u, v;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp
new file mode 100644
index 0000000..0e2cbc9
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graph-isomorphism/graph-isomorphism.7.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// There should be results where (u) = (w) (i.e. only edge adjacency should be preserved).
+SET  `graphix.match-evaluation` "homomorphism";
+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.Friends F
+                        SELECT F.user_id, F.friend )
+MATCH       (u)->(v), (v)->(w)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id
+ORDER BY    u, v, w;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.1.ddl.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.1.ddl.sqlpp
index 9667fd6..b5525dc 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.1.ddl.sqlpp
@@ -17,6 +17,21 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       UsersType
+AS                { user_id: bigint };
+CREATE DATASET    Users (UsersType)
+PRIMARY KEY       user_id;
+
+CREATE TYPE       FriendsType
+AS                { user_id: bigint, friend: bigint };
+CREATE DATASET    Friends (FriendsType)
+PRIMARY KEY       user_id, friend;
+
+CREATE TYPE       ReviewsType
+AS                { review_id: string };
+CREATE DATASET    Reviews (ReviewsType)
+PRIMARY KEY       review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.2.update.sqlpp
similarity index 62%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.2.update.sqlpp
index 9667fd6..b36158c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.2.update.sqlpp
@@ -17,6 +17,26 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 },
+  { "user_id": 5 },
+  { "user_id": 6 }
+];
+
+INSERT INTO       Reviews [
+  { "review_id": "A", "user_id": 1 },
+  { "review_id": "B", "user_id": 2 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 3, "friend": 4 },
+  { "user_id": 4, "friend": 5 },
+  { "user_id": 5, "friend": 6 }
+];
\ 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
new file mode 100644
index 0000000..35463a2
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.3.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Test each simple vertex function (label, key, properties, 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 )
+MATCH       (n)
+SELECT      VERTEX_KEY(n) AS vertexKey,
+            LABEL(n) AS vertexLabel,
+            VERTEX_PROPERTIES(n) AS vertexProperties,
+            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
new file mode 100644
index 0000000..975b3f8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.4.query.sqlpp
@@ -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.
+ */
+
+// Test each simple edge function (direction, label, keys, detail, properties).
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            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]-(n2)
+SELECT      DIRECTION(e) AS direction,
+            SOURCE_KEY(e) AS sourceKeyValue,
+            DEST_KEY(e) AS destKeyValue,
+            SOURCE_VERTEX(e, n1, n2) AS sourceVertex,
+            DEST_VERTEX(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
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
new file mode 100644
index 0000000..3595efb
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/graphix-functions/graphix-functions.5.query.sqlpp
@@ -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.
+ */
+
+// 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 ),
+            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
+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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.1.ddl.sqlpp
index 9667fd6..96ec87c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.1.ddl.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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/inference-resolution/inference-resolution.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.2.update.sqlpp
new file mode 100644
index 0000000..0b7d8c5
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.2.update.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 4, "friend": 2 },
+  { "user_id": 3, "friend": 2 }
+];
+
+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 }
+];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
new file mode 100644
index 0000000..700fe8f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.3.query.sqlpp
@@ -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.
+ */
+
+-- param max-warnings:string=1
+
+// We should be able to resolve both e and n, using the edge direction.
+SET         `graphix.resolver` "inference-based";
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS ( FROM    Yelp.Users U
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id ),
+            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
+                   SOURCE KEY (user_id)
+                   DESTINATION KEY (friend)
+                   AS ( FROM    Yelp.Friends F
+                        SELECT  F.user_id, F.friend ),
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)-[e]->(n)
+SELECT      DISTINCT LABEL(e) AS e_label,
+                     LABEL(n) AS n_label;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp
new file mode 100644
index 0000000..5533ede
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.4.query.sqlpp
@@ -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.
+ */
+
+-- param max-warnings:string=1
+
+// We should be able to resolve all elements (e, n, f, m), using the edge direction of the last edge in our pattern.
+SET         `graphix.resolver` "inference-based";
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS ( FROM    Yelp.Users U
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id ),
+            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
+                   SOURCE KEY (user_id)
+                   DESTINATION KEY (friend)
+                   AS ( FROM    Yelp.Friends F
+                        SELECT  F.user_id, F.friend ),
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (m)-[f]->(n)-[e]->(u:User)
+SELECT      DISTINCT LABEL(e) AS e_label,
+                     LABEL(n) AS n_label,
+                     LABEL(f) AS f_label,
+                     LABEL(m) AS m_label;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp
new file mode 100644
index 0000000..df7e676
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.5.query.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- param max-warnings:string=1
+
+// We should be able to resolve all elements (e, n, f, last direction), using the edge direction of the first pattern.
+SET         `graphix.resolver` "inference-based";
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS ( FROM    Yelp.Users U
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id ),
+            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
+                   SOURCE KEY (user_id)
+                   DESTINATION KEY (friend)
+                   AS ( FROM    Yelp.Friends F
+                        SELECT  F.user_id, F.friend ),
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)-[e]->(n),
+            (n)-[f]-(r:Review)
+SELECT      DISTINCT LABEL(e) AS e_label,
+                     LABEL(n) AS n_label,
+                     LABEL(f) AS f_label,
+                     DIRECTION(f) AS f_direction;
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
new file mode 100644
index 0000000..3d6e9a9
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/inference-resolution/inference-resolution.6.query.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- param max-warnings:string=1
+
+// We should be able to resolve all elements (e, n, f, last direction), using the edge direction of the first pattern.
+SET         `graphix.resolver` "inference-based";
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS ( FROM    Yelp.Users U
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id ),
+            EDGE   (:User)-[:FRIENDS_WITH]->(:User)
+                   SOURCE KEY (user_id)
+                   DESTINATION KEY (friend)
+                   AS ( FROM    Yelp.Friends F
+                        SELECT  F.user_id, F.friend ),
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)-[e:{2,2}]->(n) // (u:User)-[]->()-[]->(n)
+UNNEST      e AS edgeRecord
+LET         ee = edgeRecord.`Edge`
+SELECT      DISTINCT LABEL(ee) AS e_label,
+                     LABEL(n) AS n_label,
+                     LEN(e) AS e_length;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.1.ddl.sqlpp
index 9667fd6..96ec87c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.1.ddl.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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/left-match/left-match.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp
new file mode 100644
index 0000000..e85ca82
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.2.update.sqlpp
@@ -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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1, "group_id": 3 },
+  { "user_id": 2, "group_id": 3 },
+  { "user_id": 3, "group_id": 3 }
+];
+INSERT INTO       Reviews [
+  { "review_id": "A", "user_id": 1, "group_id": 3 },
+  { "review_id": "B", "user_id": 2, "group_id": 3 },
+  { "review_id": "C", "user_id": 1, "group_id": 3 }
+];
+INSERT INTO       Friends [
+  { "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/left-match/left-match.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.3.query.sqlpp
new file mode 100644
index 0000000..69e3592
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.3.query.sqlpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+-- LEFT-MATCH query, with source 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 = 3
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        WHERE   R.group_id = 3
+                        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 = 3
+                        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 = 3
+                        SELECT  F.user_id, F.friend )
+MATCH       (u1:User)<-[mb:MADE_BY]-(r:Review)
+LEFT MATCH  (u2:User)<-[fw:FRIENDS_WITH]-(u1)
+SELECT      u1.user_id AS u1_user_id,
+            fw.user_id AS fw_user_id,
+            fw.friend AS fw_friend,
+            u2.user_id AS u2_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
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.4.query.sqlpp
new file mode 100644
index 0000000..f4fe1cc
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.4.query.sqlpp
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+-- 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 = 4
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        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 = 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 = 4
+                        SELECT  F.user_id, F.friend )
+MATCH       (u2:User)<-[fw:FRIENDS_WITH]-(u1:User)
+LEFT MATCH  (u1)<-[mb:MADE_BY]-(r:Review)
+SELECT      u1.user_id AS u1_user_id,
+            fw.user_id AS fw_user_id,
+            fw.friend AS fw_friend,
+            u2.user_id AS u2_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
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
new file mode 100644
index 0000000..3f00813
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.5.query.sqlpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+-- 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 = 5
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        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 = 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 = 5
+                        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)
+SELECT      u1.user_id AS u1_user_id,
+            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,
+            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/left-match/left-match.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.query.sqlpp
new file mode 100644
index 0000000..810e5fe
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/left-match/left-match.6.query.sqlpp
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+-- Double LEFT-MATCH query.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS ( FROM    Yelp.Users U
+                        WHERE   U.group_id = 6
+                        SELECT  U.user_id ),
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS ( FROM    Yelp.Reviews R
+                        WHERE   R.group_id = 6
+                        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 = 6
+                        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 = 6
+                        SELECT  F.user_id, F.friend )
+MATCH       (u2:User)<-[fw1:FRIENDS_WITH]-(u1:User)
+LEFT MATCH  (u2)<-[mb:MADE_BY]-(r:Review)
+LEFT MATCH  (u2)-[fw2:FRIENDS_WITH]->(u3:User)
+SELECT      u1.user_id AS u1_user_id,
+            fw1.user_id AS fw1_user_id,
+            fw1.friend AS fw1_friend,
+            u2.user_id AS u2_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,
+            u3.user_id AS u3_user_id
+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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp
index 9667fd6..96ec87c 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.1.ddl.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
index 9667fd6..24fdd88 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.2.update.sqlpp
@@ -17,6 +17,26 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 4, "friend": 2 },
+  { "user_id": 3, "friend": 2 }
+];
+
+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 }
+];
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp
index 9667fd6..6b394d6 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.3.query.sqlpp
@@ -17,6 +17,13 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+-- 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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp
similarity index 63%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp
index 9667fd6..e78aa4b 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.4.query.sqlpp
@@ -17,6 +17,18 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+-- 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
new file mode 100644
index 0000000..1d4b4c2
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/minimal-resolution/minimal-resolution.5.query.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+-- 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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.1.ddl.sqlpp
similarity index 75%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.1.ddl.sqlpp
index 9667fd6..91a6fd9 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.1.ddl.sqlpp
@@ -17,6 +17,12 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE    TestDataverse IF EXISTS;
+CREATE DATAVERSE  TestDataverse;
+USE               TestDataverse;
+
+CREATE TYPE       GenericType
+AS                { _id: uuid };
+
+CREATE DATASET    GenericDataset (GenericType)
+PRIMARY KEY       _id AUTOGENERATED;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.10.query.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.10.query.sqlpp
index 9667fd6..bb550a0 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.10.query.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that scoping errors are recognized (in SELECT-CLAUSE).
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1]-(v2:Vertex1)
+SELECT      v1, v2, invalidVariable;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.11.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.11.query.sqlpp
new file mode 100644
index 0000000..7ecaf84
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.11.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Verify that scoping errors are recognized (in LET-CLAUSE).
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1]-(v2:Vertex1)
+LET         myValue = invalidVariable
+SELECT      v1, v2, myValue;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.12.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.12.query.sqlpp
new file mode 100644
index 0000000..a1db50d
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.12.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Verify that scoping errors are recognized (in JOIN-CLAUSE).
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1]-(v2:Vertex1)
+JOIN        ( FROM GenericDataset GD
+              SELECT GD._id ) GD
+ON          GD._id = v1._id AND invalidVariable = 3
+SELECT      v1, v2, GD;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.2.query.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.2.query.sqlpp
index 9667fd6..95a4bb5 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.2.query.sqlpp
@@ -17,6 +17,16 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that a vertex cannot be defined with a label more than once.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v:Vertex1)-[]-(),
+            (v:Vertex1)-[]-()
+SELECT      v;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.3.query.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.3.query.sqlpp
index 9667fd6..3f397af 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.3.query.sqlpp
@@ -17,6 +17,16 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that an edge cannot be defined more than once.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1)-[e]-(v3),
+            (v2)-[e]-(v4)
+SELECT      v1, v2, v3, v4, e;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.4.query.sqlpp
new file mode 100644
index 0000000..3ca72bb
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.4.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Verify that the vertex label used in a query exists in the graph schema.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:NonExistentLabel)-[e1]-(v3),
+            (v2)-[e2]-(v4)
+SELECT      v1, v2, v3, v4, e1, e2;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.5.query.sqlpp
new file mode 100644
index 0000000..d3207d1
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.5.query.sqlpp
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Verify that the edge label used in a query exists in the graph schema.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[e1:NON_EXISTENT_EDGE]-(v3),
+            (v2)-[e2]-(v4)
+SELECT      v1, v2, v3, v4, e1, e2;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.6.query.sqlpp
similarity index 75%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.6.query.sqlpp
index 9667fd6..5b5cdf7 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.6.query.sqlpp
@@ -17,6 +17,10 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that a schema constructor exists when a query edge exists.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset
+MATCH       (v1:Vertex1)-[]-(v3)
+SELECT      v1, v3;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.7.query.sqlpp
similarity index 61%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.7.query.sqlpp
index 9667fd6..abb140e 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.7.query.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that the maximum number hops for an edge is not zero.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1{,0}]-(v2:Vertex1)
+SELECT      v1, v2;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.8.query.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.8.query.sqlpp
index 9667fd6..bb6f2b0 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.8.query.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Verify that the maximum number of hops is never less than the minimum.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1{4,2}]-(v2:Vertex1)
+SELECT      v1, v2;
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
new file mode 100644
index 0000000..ea9aeb8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/on-query-error/on-query-error.9.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Verify that our graph schema is valid for anonymous graphs.
+USE         TestDataverse;
+FROM GRAPH  VERTEX  (:Vertex1)
+                    PRIMARY KEY (_id)
+                    AS GenericDataset,
+            VERTEX  (:Vertex1)
+                    PRIMARY KEY (_other_id)
+                    AS GenericDataset,
+            EDGE    (:Vertex1)-[:EDGE_1]->(:Vertex1)
+                    SOURCE KEY (_id)
+                    DESTINATION KEY (_to_id)
+                    AS ( FROM    GenericDataset GD
+                         SELECT  GD._id, GD._to_id )
+MATCH       (v1:Vertex1)-[:EDGE_1]-(v2:Vertex1)
+SELECT      v1, v2;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.1.ddl.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.1.ddl.sqlpp
index 9667fd6..4dca0dc 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.1.ddl.sqlpp
@@ -17,6 +17,21 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       UsersType
+AS                { user_id: bigint };
+CREATE DATASET    Users (UsersType)
+PRIMARY KEY       user_id;
+
+CREATE TYPE       FriendsType
+AS                { user_id: bigint, friend: bigint };
+CREATE DATASET    Friends (FriendsType)
+PRIMARY KEY       user_id, friend;
+
+CREATE TYPE       ReviewsType
+AS                { review_id: string };
+CREATE DATASET    Reviews (ReviewsType)
+PRIMARY KEY       review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.2.update.sqlpp
new file mode 100644
index 0000000..96a4ef6
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.2.update.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1, "best_friend": 2 },
+  { "user_id": 2, "best_friend": 3 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2, "friend_group": "A" },
+  { "user_id": 2, "friend": 3, "friend_group": "A" },
+  { "user_id": 3, "friend": 4, "friend_group": "B" }
+];
+
+INSERT INTO       Reviews [
+  { "review_id": "A", "user_id": 1 },
+  { "review_id": "B", "user_id": 1 },
+  { "review_id": "C", "user_id": 1 },
+  { "review_id": "D", "user_id": 2 },
+  { "review_id": "E", "user_id": 3 },
+  { "review_id": "F", "user_id": 4 }
+];
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
similarity index 72%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
index 9667fd6..e110262 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.3.query.sqlpp
@@ -17,6 +17,12 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+// Single vertex path.
+FROM GRAPH  VERTEX (:User)
+                   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);
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
new file mode 100644
index 0000000..a96f62a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.4.query.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Single edge path.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-[:MADE_BY]-(r:Review) AS p
+UNNEST      p AS edgeRecord
+SELECT      PATH_HOP_COUNT(p) AS pathLength,
+            edgeRecord
+ORDER BY    u.user_id,
+            r.review_d,
+            VERTEX_KEY(edgeRecord.LeftVertex),
+            SOURCE_KEY(edgeRecord.`Edge`);
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
new file mode 100644
index 0000000..13078e0
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.5.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Two bidirectional edges path.
+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.Friends F
+                        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
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
new file mode 100644
index 0000000..ffef1a7
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/path-variable/path-variable.6.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// An undirected variable-hop sub-path located within a path.
+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.Friends F
+                        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
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
similarity index 60%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
index 9667fd6..4dca0dc 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.1.ddl.sqlpp
@@ -17,6 +17,21 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       UsersType
+AS                { user_id: bigint };
+CREATE DATASET    Users (UsersType)
+PRIMARY KEY       user_id;
+
+CREATE TYPE       FriendsType
+AS                { user_id: bigint, friend: bigint };
+CREATE DATASET    Friends (FriendsType)
+PRIMARY KEY       user_id, friend;
+
+CREATE TYPE       ReviewsType
+AS                { review_id: string };
+CREATE DATASET    Reviews (ReviewsType)
+PRIMARY KEY       review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp
new file mode 100644
index 0000000..d42899e
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.2.update.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 3, "friend": 4 },
+  { "user_id": 1, "friend": 3 }
+];
+
+INSERT INTO       Reviews [
+  { "review_id": "A", "user_id": 1 },
+  { "review_id": "B", "user_id": 1 },
+  { "review_id": "C", "user_id": 1 },
+  { "review_id": "D", "user_id": 2 },
+  { "review_id": "E", "user_id": 3 },
+  { "review_id": "F", "user_id": 4 }
+];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
new file mode 100644
index 0000000..8122ce1
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.3.query.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+// Subquery expressing anti-join of patterns.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-(r)
+WHERE       NOT EXISTS ( FROM GRAPH   VERTEX (:User)
+                                      PRIMARY KEY (user_id)
+                                      AS ( FROM    Yelp.Users U
+                                           WHERE   U.user_id = 1
+                                           SELECT  VALUE U )
+                          MATCH       (innerU:User)
+                          WHERE       innerU = u
+                          SELECT      VALUE 1 )
+SELECT      u.user_id
+ORDER BY    u.user_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.4.query.sqlpp
new file mode 100644
index 0000000..e103b44
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.4.query.sqlpp
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Subquery expressing anti-join of patterns, using aliases.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-(r)
+LET         myUser = u,
+            myReview = r
+WHERE       NOT EXISTS ( FROM GRAPH   VERTEX (:User)
+                                      PRIMARY KEY (user_id)
+                                      AS ( FROM    Yelp.Users U
+                                           WHERE   U.user_id = 1
+                                           SELECT  VALUE U )
+                          MATCH       (innerU:User)
+                          LET         myInnerUser = innerU
+                          WHERE       myInnerUser = myUser
+                          SELECT      VALUE 1 )
+SELECT      myUser.user_id, myReview.review_id
+ORDER BY    u.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
new file mode 100644
index 0000000..40e57c3
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.5.query.sqlpp
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Subquery + GROUP BY expressing (bounded) shortest-path.
+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.Friends F
+                        SELECT  VALUE F )
+MATCH       (u1)-[:{1,3}]->(u2) AS p
+GROUP BY    u1, u2
+GROUP AS    g
+LET         shortestPath = ( FROM      g AS gi
+                             SELECT    VALUE ( FROM    PATH_VERTICES(gi.p) pv
+                                               SELECT  VALUE pv.user_id )
+                             ORDER BY  PATH_HOP_COUNT(gi.p) ASC
+                             LIMIT     1 )
+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
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
new file mode 100644
index 0000000..1404a75
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.6.query.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+// Subquery + GROUP BY expressing (bounded) shortest-path, using aliases.
+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.Friends F
+                        SELECT  VALUE F )
+MATCH       (u1)-[:{1,3}]->(u2) AS p
+LET         pathHopCount = PATH_HOP_COUNT(p),
+            pathVertices = PATH_VERTICES(p),
+            myUser1 = u1,
+            myUser2 = u2
+GROUP BY    myUser1, myUser2
+GROUP AS    g
+SELECT      myUser1.user_id AS u1_user_id,
+            myUser2.user_id AS u2_user_id,
+            ( FROM      g AS gi
+              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
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.7.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.7.query.sqlpp
new file mode 100644
index 0000000..13c5e00
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.7.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// Scope should be respected when a variable is re-declared in a non-graph query.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-(r)
+WHERE       EXISTS ( FROM    Yelp.Users u
+                     WHERE   u.user_id = 1
+                     SELECT  VALUE 1 )
+SELECT      u.user_id, r.review_id
+ORDER BY    u.user_id, r.review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.8.query.sqlpp
new file mode 100644
index 0000000..0c97f2c
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/scope-checking/scope-checking.8.query.sqlpp
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Scope should be respected when a variable is re-declared in another graph query.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-(r)
+WHERE       EXISTS ( FROM GRAPH  VERTEX (:User)
+                                 PRIMARY KEY (user_id)
+                                 AS Yelp.Users
+                     MATCH       (u:User)
+                     WHERE       u.user_id = 1
+                     SELECT      VALUE 1 )
+SELECT      u.user_id, r.review_id
+ORDER BY    u.user_id, r.review_id;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.1.ddl.sqlpp
index 9667fd6..271a6a8 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.1.ddl.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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;
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
new file mode 100644
index 0000000..21365f1
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.2.update.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1, "friends": [2, 3], "best_friend": 2 },
+  { "user_id": 2, "friends": [1] },
+  { "user_id": 3, "friends": [1], "best_friend": 1 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 4, "friend": 1 },
+  { "user_id": 4, "friend": 2 },
+  { "user_id": 5, "friend": null }
+];
+
+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 }
+];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.3.query.sqlpp
new file mode 100644
index 0000000..e69655d
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.3.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+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 )
+MATCH       (u2:User)<-[fw:FRIENDS_WITH]-(u1:User)
+SELECT      u1.user_id AS u1_user_id,
+            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, u2, fw;
\ No newline at end of file
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
new file mode 100644
index 0000000..9e57a33
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.4.query.sqlpp
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Two edge definitions, single vertex definition on both sides.
+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;
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
new file mode 100644
index 0000000..ff0d317
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.5.query.sqlpp
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Two edge definitions, one vertex definition on the source and two vertex definitions on the destination.
+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)
+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
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
new file mode 100644
index 0000000..298b5c8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-1-edge/simple-1-edge.6.query.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.1.ddl.sqlpp
index 9667fd6..271a6a8 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.1.ddl.sqlpp
@@ -17,6 +17,15 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+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;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.2.update.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.2.update.sqlpp
new file mode 100644
index 0000000..61788ee
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.2.update.sqlpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1, "best_friend": 2 },
+  { "user_id": 2, "best_friend": 3 },
+  { "user_id": 3 },
+  { "user_id": 4 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2, "friend_group": "A" },
+  { "user_id": 2, "friend": 3, "friend_group": "A" },
+  { "user_id": 3, "friend": 4, "friend_group": "B" }
+];
+
+INSERT INTO       Reviews [
+  { "review_id": "A", "user_id": 1 },
+  { "review_id": "B", "user_id": 1 },
+  { "review_id": "C", "user_id": 1 },
+  { "review_id": "D", "user_id": 2 },
+  { "review_id": "E", "user_id": 2 },
+  { "review_id": "F", "user_id": 3 }
+];
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.3.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.3.query.sqlpp
new file mode 100644
index 0000000..dd474cc
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.3.query.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+// Two query edges, single vertex definition on both sides.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-[mb1:MADE_BY]-(r1:Review),
+            (u)<-[mb2:MADE_BY]-(r2:Review)
+SELECT      u.user_id AS u_user_id,
+            r1.review_id AS r1_review_id,
+            r2.review_id AS r2_review_id,
+            mb1.user_id AS mb1_user_id,
+            mb2.user_id AS mb2_user_id,
+            mb1.review_id AS mb1_review_id,
+            mb2.review_id AS mb2_review_id
+ORDER BY    u_user_id,
+            r1_review_id,
+            r2_review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.4.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.4.query.sqlpp
new file mode 100644
index 0000000..e0655b2
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.4.query.sqlpp
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Three query edges, single vertex definition on both sides.
+FROM GRAPH  VERTEX (:User)
+                   PRIMARY KEY (user_id)
+                   AS Yelp.Users,
+            VERTEX (:Review)
+                   PRIMARY KEY (review_id)
+                   AS Yelp.Reviews,
+            EDGE   (:Review)-[:MADE_BY]->(:User)
+                   SOURCE KEY (review_id)
+                   DESTINATION KEY (user_id)
+                   AS ( FROM    Yelp.Reviews R
+                        SELECT  R.review_id, R.user_id )
+MATCH       (u:User)<-[mb1:MADE_BY]-(r1:Review),
+            (u)<-[mb2:MADE_BY]-(r2:Review),
+            (u)<-[mb3:MADE_BY]-(r3:Review)
+SELECT      u.user_id AS u_user_id,
+            r1.review_id AS r1_review_id,
+            r2.review_id AS r2_review_id,
+            r3.review_id AS r3_review_id,
+            mb1.user_id AS mb1_user_id,
+            mb2.user_id AS mb2_user_id,
+            mb3.user_id AS mb3_user_id,
+            mb1.review_id AS mb1_review_id,
+            mb2.review_id AS mb2_review_id,
+            mb3.review_id AS mb3_review_id
+ORDER BY    u_user_id,
+            r1_review_id,
+            r2_review_id,
+            r3_review_id;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.5.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.5.query.sqlpp
new file mode 100644
index 0000000..7d12415
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.5.query.sqlpp
@@ -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.
+ */
+
+// Two bidirectional query edges, single vertex definition for all vertices.
+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.Friends F
+                        WHERE   F.friend_group = "A"
+                        SELECT  F.user_id, F.friend )
+MATCH       (u)-[e1]-(v)-[e2]-(w)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id,
+            e1.user_id AS e1_user_id,
+            e1.friend AS e1_friend,
+            e2.user_id AS e2_user_id,
+            e2.friend AS e2_friend
+ORDER BY    u_user_id,
+            v_user_id,
+            w_user_id,
+            e1_user_id,
+            e1_friend,
+            e2_user_id,
+            e2_friend;
diff --git a/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.6.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.6.query.sqlpp
new file mode 100644
index 0000000..db9f1d1
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.6.query.sqlpp
@@ -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.
+ */
+
+// Two directed query edges, single vertex definition for all vertices.
+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.Friends F
+                        WHERE   F.friend_group = "A"
+                        SELECT  F.user_id, F.friend )
+MATCH       (u)-[e1]->(v)-[e2]->(w)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id,
+            e1.user_id AS e1_user_id,
+            e1.friend AS e1_friend,
+            e2.user_id AS e2_user_id,
+            e2.friend AS e2_friend
+ORDER BY    u_user_id,
+            v_user_id,
+            w_user_id,
+            e1_user_id,
+            e1_friend,
+            e2_user_id,
+            e2_friend;
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
new file mode 100644
index 0000000..56a0672
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/simple-n-edge/simple-n-edge.7.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+// Three bidirectional query edges, single vertex definition for all vertices.
+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.Friends F
+                        SELECT  F.* )
+MATCH       (u)-(v)-(w)-(y)
+SELECT      u.user_id AS u_user_id,
+            v.user_id AS v_user_id,
+            w.user_id AS w_user_id,
+            y.user_id AS y_user_id
+ORDER BY    u_user_id,
+            v_user_id,
+            w_user_id,
+            y_user_id;
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.1.ddl.sqlpp
similarity index 67%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.1.ddl.sqlpp
index 9667fd6..00df90a 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.1.ddl.sqlpp
@@ -17,6 +17,16 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+DROP DATAVERSE    Yelp IF EXISTS;
+CREATE DATAVERSE  Yelp;
+USE               Yelp;
+
+CREATE TYPE       UsersType
+AS                { user_id: bigint };
+CREATE DATASET    Users (UsersType)
+PRIMARY KEY       user_id;
+
+CREATE TYPE       FriendsType
+AS                { user_id: bigint, friend: bigint };
+CREATE DATASET    Friends (FriendsType)
+PRIMARY KEY       user_id, friend;
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.2.update.sqlpp
similarity index 68%
copy from asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
copy to asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.2.update.sqlpp
index 9667fd6..5780dc6 100644
--- a/asterix-graphix/src/test/resources/metadata/queries/graphix/yelp-example/yelp-example.8.query.sqlpp
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.2.update.sqlpp
@@ -17,6 +17,21 @@
  * under the License.
  */
 
-FROM    `Metadata`.`Graph` G
-WHERE   G.DataverseName IN [ "Yelp", "Yelp_A", "Yelp_B" ]
-SELECT  VALUE { "count": COUNT(*) };
+USE               Yelp;
+
+INSERT INTO       Users [
+  { "user_id": 1 },
+  { "user_id": 2 },
+  { "user_id": 3 },
+  { "user_id": 4 },
+  { "user_id": 5 },
+  { "user_id": 6 }
+];
+
+INSERT INTO       Friends [
+  { "user_id": 1, "friend": 2 },
+  { "user_id": 2, "friend": 3 },
+  { "user_id": 3, "friend": 4 },
+  { "user_id": 4, "friend": 5 },
+  { "user_id": 5, "friend": 6 }
+];
\ No newline at end of file
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
new file mode 100644
index 0000000..c5c1d8d
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.3.query.sqlpp
@@ -0,0 +1,37 @@
+/*
+ * 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 directed edge definition of one to three hops.
+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.Friends F
+                        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)
+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
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
new file mode 100644
index 0000000..83a78b4
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/queries/graphix/variable-sub-path/variable-sub-path.4.query.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// One undirected edge definition of one to two hops.
+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.Friends F
+                        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)
+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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.3.adm
new file mode 100644
index 0000000..bf58f6a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.3.adm
@@ -0,0 +1,4 @@
+{ "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/dangling-vertices/dangling-vertices.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.4.adm
new file mode 100644
index 0000000..b3d4d9f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.4.adm
@@ -0,0 +1,12 @@
+{ "u1_user_id": 1, "u2_user_id": 2 }
+{ "u1_user_id": 1, "u2_user_id": 3 }
+{ "u1_user_id": 1, "u2_user_id": 4 }
+{ "u1_user_id": 2, "u2_user_id": 1 }
+{ "u1_user_id": 2, "u2_user_id": 3 }
+{ "u1_user_id": 2, "u2_user_id": 4 }
+{ "u1_user_id": 3, "u2_user_id": 1 }
+{ "u1_user_id": 3, "u2_user_id": 2 }
+{ "u1_user_id": 3, "u2_user_id": 4 }
+{ "u1_user_id": 4, "u2_user_id": 1 }
+{ "u1_user_id": 4, "u2_user_id": 2 }
+{ "u1_user_id": 4, "u2_user_id": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.5.adm
new file mode 100644
index 0000000..d547241
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.5.adm
@@ -0,0 +1,24 @@
+{ "user_id": 1, "review_id": "A" }
+{ "user_id": 1, "review_id": "B" }
+{ "user_id": 1, "review_id": "C" }
+{ "user_id": 1, "review_id": "D" }
+{ "user_id": 1, "review_id": "E" }
+{ "user_id": 1, "review_id": "F" }
+{ "user_id": 2, "review_id": "A" }
+{ "user_id": 2, "review_id": "B" }
+{ "user_id": 2, "review_id": "C" }
+{ "user_id": 2, "review_id": "D" }
+{ "user_id": 2, "review_id": "E" }
+{ "user_id": 2, "review_id": "F" }
+{ "user_id": 3, "review_id": "A" }
+{ "user_id": 3, "review_id": "B" }
+{ "user_id": 3, "review_id": "C" }
+{ "user_id": 3, "review_id": "D" }
+{ "user_id": 3, "review_id": "E" }
+{ "user_id": 3, "review_id": "F" }
+{ "user_id": 4, "review_id": "A" }
+{ "user_id": 4, "review_id": "B" }
+{ "user_id": 4, "review_id": "C" }
+{ "user_id": 4, "review_id": "D" }
+{ "user_id": 4, "review_id": "E" }
+{ "user_id": 4, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.6.adm
new file mode 100644
index 0000000..295b176
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/dangling-vertices/dangling-vertices.6.adm
@@ -0,0 +1,5 @@
+{ "user_id": 1, "review_id": "A" }
+{ "user_id": 1, "review_id": "B" }
+{ "user_id": 2, "review_id": "C" }
+{ "user_id": 3, "review_id": "D" }
+{ "user_id": 4, "review_id": "E" }
\ 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
new file mode 100644
index 0000000..45a5043
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.3.adm
@@ -0,0 +1,8 @@
+{ "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
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
new file mode 100644
index 0000000..d7b0151
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.4.adm
@@ -0,0 +1,9 @@
+{ "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
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
new file mode 100644
index 0000000..eb23c4f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.5.adm
@@ -0,0 +1,8 @@
+{ "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
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
new file mode 100644
index 0000000..6b486c7
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.6.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..8d03172
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/fixed-sub-path/fixed-sub-path.8.adm
@@ -0,0 +1,20 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.3.adm
new file mode 100644
index 0000000..b9a3cc8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.3.adm
@@ -0,0 +1,12 @@
+{ "u_user_id": 1, "v_user_id": 2 }
+{ "u_user_id": 1, "v_user_id": 3 }
+{ "u_user_id": 1, "v_user_id": 4 }
+{ "u_user_id": 2, "v_user_id": 1 }
+{ "u_user_id": 2, "v_user_id": 3 }
+{ "u_user_id": 2, "v_user_id": 4 }
+{ "u_user_id": 3, "v_user_id": 1 }
+{ "u_user_id": 3, "v_user_id": 2 }
+{ "u_user_id": 3, "v_user_id": 4 }
+{ "u_user_id": 4, "v_user_id": 1 }
+{ "u_user_id": 4, "v_user_id": 2 }
+{ "u_user_id": 4, "v_user_id": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.4.adm
new file mode 100644
index 0000000..c3ad22c
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.4.adm
@@ -0,0 +1,24 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3 }
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 4 }
+{ "u_user_id": 1, "v_user_id": 3, "w_user_id": 2 }
+{ "u_user_id": 1, "v_user_id": 3, "w_user_id": 4 }
+{ "u_user_id": 1, "v_user_id": 4, "w_user_id": 2 }
+{ "u_user_id": 1, "v_user_id": 4, "w_user_id": 3 }
+{ "u_user_id": 2, "v_user_id": 1, "w_user_id": 3 }
+{ "u_user_id": 2, "v_user_id": 1, "w_user_id": 4 }
+{ "u_user_id": 2, "v_user_id": 3, "w_user_id": 1 }
+{ "u_user_id": 2, "v_user_id": 3, "w_user_id": 4 }
+{ "u_user_id": 2, "v_user_id": 4, "w_user_id": 1 }
+{ "u_user_id": 2, "v_user_id": 4, "w_user_id": 3 }
+{ "u_user_id": 3, "v_user_id": 1, "w_user_id": 2 }
+{ "u_user_id": 3, "v_user_id": 1, "w_user_id": 4 }
+{ "u_user_id": 3, "v_user_id": 2, "w_user_id": 1 }
+{ "u_user_id": 3, "v_user_id": 2, "w_user_id": 4 }
+{ "u_user_id": 3, "v_user_id": 4, "w_user_id": 1 }
+{ "u_user_id": 3, "v_user_id": 4, "w_user_id": 2 }
+{ "u_user_id": 4, "v_user_id": 1, "w_user_id": 2 }
+{ "u_user_id": 4, "v_user_id": 1, "w_user_id": 3 }
+{ "u_user_id": 4, "v_user_id": 2, "w_user_id": 1 }
+{ "u_user_id": 4, "v_user_id": 2, "w_user_id": 3 }
+{ "u_user_id": 4, "v_user_id": 3, "w_user_id": 1 }
+{ "u_user_id": 4, "v_user_id": 3, "w_user_id": 2 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.5.adm
new file mode 100644
index 0000000..df6db5f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.5.adm
@@ -0,0 +1 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.6.adm
new file mode 100644
index 0000000..0534569
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.6.adm
@@ -0,0 +1,16 @@
+{ "u_user_id": 1, "v_user_id": 1 }
+{ "u_user_id": 1, "v_user_id": 2 }
+{ "u_user_id": 1, "v_user_id": 3 }
+{ "u_user_id": 1, "v_user_id": 4 }
+{ "u_user_id": 2, "v_user_id": 1 }
+{ "u_user_id": 2, "v_user_id": 2 }
+{ "u_user_id": 2, "v_user_id": 3 }
+{ "u_user_id": 2, "v_user_id": 4 }
+{ "u_user_id": 3, "v_user_id": 1 }
+{ "u_user_id": 3, "v_user_id": 2 }
+{ "u_user_id": 3, "v_user_id": 3 }
+{ "u_user_id": 3, "v_user_id": 4 }
+{ "u_user_id": 4, "v_user_id": 1 }
+{ "u_user_id": 4, "v_user_id": 2 }
+{ "u_user_id": 4, "v_user_id": 3 }
+{ "u_user_id": 4, "v_user_id": 4 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.7.adm
new file mode 100644
index 0000000..5ba7e67
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graph-isomorphism/graph-isomorphism.7.adm
@@ -0,0 +1,3 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 1 }
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3 }
+{ "u_user_id": 2, "v_user_id": 1, "w_user_id": 2 }
\ 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
new file mode 100644
index 0000000..d746e73
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.3.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..a4b2231
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.4.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..6fab046
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/graphix-functions/graphix-functions.5.adm
@@ -0,0 +1,4 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.3.adm
new file mode 100644
index 0000000..aea519f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.3.adm
@@ -0,0 +1 @@
+{ "e_label": "FRIENDS_WITH", "n_label": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.4.adm
new file mode 100644
index 0000000..20fd92b
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.4.adm
@@ -0,0 +1,2 @@
+{ "e_label": "FRIENDS_WITH", "n_label": "User", "f_label": "FRIENDS_WITH", "m_label": "User" }
+{ "e_label": "FRIENDS_WITH", "n_label": "User", "f_label": "MADE_BY", "m_label": "Review" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.5.adm
new file mode 100644
index 0000000..771db0f
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.5.adm
@@ -0,0 +1 @@
+{ "e_label": "FRIENDS_WITH", "n_label": "User", "f_label": "MADE_BY", "f_direction": "RIGHT_TO_LEFT" }
\ 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
new file mode 100644
index 0000000..5a87275
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/inference-resolution/inference-resolution.6.adm
@@ -0,0 +1 @@
+{ "e_length": 2, "e_label": "FRIENDS_WITH", "n_label": "User" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.3.adm
new file mode 100644
index 0000000..6e6daf8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.3.adm
@@ -0,0 +1,3 @@
+{ "u1_user_id": 1, "mb_user_id": 1, "mb_review_id": "A", "r_review_id": "A" }
+{ "u1_user_id": 1, "mb_user_id": 1, "mb_review_id": "C", "r_review_id": "C" }
+{ "u1_user_id": 2, "fw_user_id": 2, "fw_friend": 3, "u2_user_id": 3, "mb_user_id": 2, "mb_review_id": "B", "r_review_id": "B" }
\ 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
new file mode 100644
index 0000000..9a221cf
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.4.adm
@@ -0,0 +1,2 @@
+{ "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
new file mode 100644
index 0000000..5c82883
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.5.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.6.adm
new file mode 100644
index 0000000..f3ea80e
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/left-match/left-match.6.adm
@@ -0,0 +1,5 @@
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 2, "u2_user_id": 2, "mb_user_id": 2, "mb_review_id": "A", "r_review_id": "A" }
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 3, "u2_user_id": 3, "mb_user_id": 3, "mb_review_id": "B", "r_review_id": "B", "fw2_user_id": 3, "fw2_friend": 4, "u3_user_id": 4 }
+{ "u1_user_id": 1, "fw1_user_id": 1, "fw1_friend": 3, "u2_user_id": 3, "mb_user_id": 3, "mb_review_id": "B", "r_review_id": "B", "fw2_user_id": 3, "fw2_friend": 5, "u3_user_id": 5 }
+{ "u1_user_id": 3, "fw1_user_id": 3, "fw1_friend": 4, "u2_user_id": 4 }
+{ "u1_user_id": 3, "fw1_user_id": 3, "fw1_friend": 5, "u2_user_id": 5 }
\ No newline at end of file
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
new file mode 100644
index 0000000..bf58f6a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.3.adm
@@ -0,0 +1,4 @@
+{ "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
new file mode 100644
index 0000000..9fd8681
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.4.adm
@@ -0,0 +1,10 @@
+{ "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
new file mode 100644
index 0000000..843d7f9
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.5.adm
@@ -0,0 +1,10 @@
+{ "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
new file mode 100644
index 0000000..b451053
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/minimal-resolution/minimal-resolution.6.adm
@@ -0,0 +1,8 @@
+{ "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
new file mode 100644
index 0000000..dc47f05
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.3.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..be295c8
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.4.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..94e906a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.5.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..ba46ed7
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/path-variable/path-variable.6.adm
@@ -0,0 +1,8 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.3.adm
new file mode 100644
index 0000000..5744f8b
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.3.adm
@@ -0,0 +1,3 @@
+{ "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/scope-checking/scope-checking.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm
new file mode 100644
index 0000000..f55b204
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.4.adm
@@ -0,0 +1,3 @@
+{ "user_id": 2, "review_id": "D" }
+{ "user_id": 3, "review_id": "E" }
+{ "user_id": 4, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
new file mode 100644
index 0000000..802c33a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.5.adm
@@ -0,0 +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
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
new file mode 100644
index 0000000..802c33a
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.6.adm
@@ -0,0 +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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm
new file mode 100644
index 0000000..9b24dd0
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.7.adm
@@ -0,0 +1,6 @@
+{ "user_id": 1, "review_id": "A" }
+{ "user_id": 1, "review_id": "B" }
+{ "user_id": 1, "review_id": "C" }
+{ "user_id": 2, "review_id": "D" }
+{ "user_id": 3, "review_id": "E" }
+{ "user_id": 4, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm
new file mode 100644
index 0000000..9b24dd0
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/scope-checking/scope-checking.8.adm
@@ -0,0 +1,6 @@
+{ "user_id": 1, "review_id": "A" }
+{ "user_id": 1, "review_id": "B" }
+{ "user_id": 1, "review_id": "C" }
+{ "user_id": 2, "review_id": "D" }
+{ "user_id": 3, "review_id": "E" }
+{ "user_id": 4, "review_id": "F" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.3.adm
new file mode 100644
index 0000000..710a380
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.3.adm
@@ -0,0 +1,4 @@
+{ "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": 3, "friends": [ 2, 3 ], "fw_user_id": 1, "fw_friend": 3 }
+{ "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": 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.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm
new file mode 100644
index 0000000..d340a90
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.4.adm
@@ -0,0 +1,6 @@
+{ "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
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
new file mode 100644
index 0000000..190ae9b
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.5.adm
@@ -0,0 +1,6 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
new file mode 100644
index 0000000..48e9814
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-1-edge/simple-1-edge.6.adm
@@ -0,0 +1,40 @@
+{ "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-n-edge/simple-n-edge.3.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.3.adm
new file mode 100644
index 0000000..8066712
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.3.adm
@@ -0,0 +1,8 @@
+{ "u_user_id": 1, "r1_review_id": "A", "r2_review_id": "B", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "A", "mb2_review_id": "B" }
+{ "u_user_id": 1, "r1_review_id": "A", "r2_review_id": "C", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "A", "mb2_review_id": "C" }
+{ "u_user_id": 1, "r1_review_id": "B", "r2_review_id": "A", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "B", "mb2_review_id": "A" }
+{ "u_user_id": 1, "r1_review_id": "B", "r2_review_id": "C", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "B", "mb2_review_id": "C" }
+{ "u_user_id": 1, "r1_review_id": "C", "r2_review_id": "A", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "C", "mb2_review_id": "A" }
+{ "u_user_id": 1, "r1_review_id": "C", "r2_review_id": "B", "mb1_user_id": 1, "mb2_user_id": 1, "mb1_review_id": "C", "mb2_review_id": "B" }
+{ "u_user_id": 2, "r1_review_id": "D", "r2_review_id": "E", "mb1_user_id": 2, "mb2_user_id": 2, "mb1_review_id": "D", "mb2_review_id": "E" }
+{ "u_user_id": 2, "r1_review_id": "E", "r2_review_id": "D", "mb1_user_id": 2, "mb2_user_id": 2, "mb1_review_id": "E", "mb2_review_id": "D" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.4.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.4.adm
new file mode 100644
index 0000000..5dc5f34
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.4.adm
@@ -0,0 +1,6 @@
+{ "u_user_id": 1, "r1_review_id": "A", "r2_review_id": "B", "r3_review_id": "C", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "A", "mb2_review_id": "B", "mb3_review_id": "C" }
+{ "u_user_id": 1, "r1_review_id": "A", "r2_review_id": "C", "r3_review_id": "B", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "A", "mb2_review_id": "C", "mb3_review_id": "B" }
+{ "u_user_id": 1, "r1_review_id": "B", "r2_review_id": "A", "r3_review_id": "C", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "B", "mb2_review_id": "A", "mb3_review_id": "C" }
+{ "u_user_id": 1, "r1_review_id": "B", "r2_review_id": "C", "r3_review_id": "A", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "B", "mb2_review_id": "C", "mb3_review_id": "A" }
+{ "u_user_id": 1, "r1_review_id": "C", "r2_review_id": "A", "r3_review_id": "B", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "C", "mb2_review_id": "A", "mb3_review_id": "B" }
+{ "u_user_id": 1, "r1_review_id": "C", "r2_review_id": "B", "r3_review_id": "A", "mb1_user_id": 1, "mb2_user_id": 1, "mb3_user_id": 1, "mb1_review_id": "C", "mb2_review_id": "B", "mb3_review_id": "A" }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.5.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.5.adm
new file mode 100644
index 0000000..e007129
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.5.adm
@@ -0,0 +1,2 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3, "e1_user_id": 1, "e1_friend": 2, "e2_user_id": 2, "e2_friend": 3 }
+{ "u_user_id": 3, "v_user_id": 2, "w_user_id": 1, "e1_user_id": 2, "e1_friend": 3, "e2_user_id": 1, "e2_friend": 2 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.6.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.6.adm
new file mode 100644
index 0000000..a3464bc
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.6.adm
@@ -0,0 +1 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3, "e1_user_id": 1, "e1_friend": 2, "e2_user_id": 2, "e2_friend": 3 }
\ No newline at end of file
diff --git a/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.7.adm b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.7.adm
new file mode 100644
index 0000000..f672b8d
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/simple-n-edge/simple-n-edge.7.adm
@@ -0,0 +1,2 @@
+{ "u_user_id": 1, "v_user_id": 2, "w_user_id": 3, "y_user_id": 4 }
+{ "u_user_id": 4, "v_user_id": 3, "w_user_id": 2, "y_user_id": 1 }
\ 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
new file mode 100644
index 0000000..5f0bdb6
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.3.adm
@@ -0,0 +1,22 @@
+{ "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
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
new file mode 100644
index 0000000..581cb48
--- /dev/null
+++ b/asterix-graphix/src/test/resources/runtimets/results/graphix/variable-sub-path/variable-sub-path.4.adm
@@ -0,0 +1,26 @@
+{ "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
diff --git a/asterix-graphix/src/test/resources/runtimets/testsuite.xml b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
index ea6db85..0ca8de6 100644
--- a/asterix-graphix/src/test/resources/runtimets/testsuite.xml
+++ b/asterix-graphix/src/test/resources/runtimets/testsuite.xml
@@ -20,21 +20,129 @@
             ResultOffsetPath="results"
             QueryOffsetPath="queries"
             QueryFileExtension=".sqlpp">
-  <test-group name="error-handling">
+  <test-group name="create-drop-error">
     <test-case FilePath="graphix">
-      <compilation-unit name="error-handling">
-        <output-dir compare="Text">error-handling</output-dir>
-        <expected-error>Cannot drop dataset (or view) TestDataverse.GenericDataset being used by graph TestGraph</expected-error>
-        <expected-error>Cannot drop function TestDataverse.TestFunction() being used by graph TestGraph</expected-error>
-        <expected-error>Cannot drop dataset (or view) TestDataverse.TestView being used by graph TestGraph</expected-error>
-        <expected-error>Cannot drop synonym TestDataverse.DatasetSynonym being used by graph TestGraph</expected-error>
-        <expected-error>Cannot drop dataverse: dataset (or view) TestDataverse2.GenericDataset being used by graph TestGraph</expected-error>
+      <compilation-unit name="create-drop-error">
+        <output-dir compare="Text">create-drop-error</output-dir>
+        <expected-error>Cannot drop DATASET TestDataverse.GenericDataset being used by GRAPH TestDataverse.TestGraph</expected-error>
+        <expected-error>Cannot drop FUNCTION TestDataverse.TestFunction() being used by GRAPH TestDataverse.TestGraph</expected-error>
+        <expected-error>Cannot drop DATASET TestDataverse.TestView being used by GRAPH TestDataverse.TestGraph</expected-error>
+        <expected-error>Cannot drop SYNONYM TestDataverse.DatasetSynonym being used by GRAPH TestDataverse.TestGraph</expected-error>
+        <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>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>
+        <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>
+        <expected-error>Cannot drop GRAPH TestDataverse.TestGraph being used by VIEW TestDataverse.TestView</expected-error>
+        <expected-error>Cannot drop GRAPH TestDataverse.TestGraph being used by FUNCTION TestDataverse.TestFunction</expected-error>
+        <expected-error>Cannot drop GRAPH TestDataverse.TestGraph being used by GRAPH TestDataverse.TestGraph2</expected-error>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="dangling-vertices">
+    <test-case FilePath="graphix">
+      <compilation-unit name="dangling-vertices">
+        <output-dir compare="Text">dangling-vertices</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="fixed-sub-path">
+    <test-case FilePath="graphix">
+      <compilation-unit name="fixed-sub-path">
+        <output-dir compare="Text">fixed-sub-path</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="graph-isomorphism">
+    <test-case FilePath="graphix">
+      <compilation-unit name="graph-isomorphism">
+        <output-dir compare="Text">graph-isomorphism</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="graphix-functions">
+    <test-case FilePath="graphix">
+      <compilation-unit name="graphix-functions">
+        <output-dir compare="Text">graphix-functions</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="inference-resolution">
+    <test-case FilePath="graphix">
+      <compilation-unit name="inference-resolution">
+        <output-dir compare="Text">inference-resolution</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="left-match">
+    <test-case FilePath="graphix">
+      <compilation-unit name="left-match">
+        <output-dir compare="Text">left-match</output-dir>
+      </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">
+        <output-dir compare="Text">on-query-error</output-dir>
+        <expected-error>Vertex $v defined with a label more than once. Labels can only be bound to vertices once.</expected-error>
+        <expected-error>Edge $e defined more than once. Edges can only connect two vertices.</expected-error>
+        <expected-error>Vertex label NonExistentLabel does not exist in the given graph schema.</expected-error>
+        <expected-error>Edge label NON_EXISTENT_EDGE does not exist in the given graph schema.</expected-error>
+        <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>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>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="path-variable">
+    <test-case FilePath="graphix">
+      <compilation-unit name="path-variable">
+        <output-dir compare="Text">path-variable</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="scope-checking">
+    <test-case FilePath="graphix">
+      <compilation-unit name="scope-checking">
+        <output-dir compare="Text">scope-checking</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="simple-1-edge">
+    <test-case FilePath="graphix">
+      <compilation-unit name="simple-1-edge">
+        <output-dir compare="Text">simple-1-edge</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="simple-n-edge">
+    <test-case FilePath="graphix">
+      <compilation-unit name="simple-n-edge">
+        <output-dir compare="Text">simple-n-edge</output-dir>
+      </compilation-unit>
+    </test-case>
+  </test-group>
+  <test-group name="variable-sub-path">
+    <test-case FilePath="graphix">
+      <compilation-unit name="variable-sub-path">
+        <output-dir compare="Text">variable-sub-path</output-dir>
       </compilation-unit>
     </test-case>
   </test-group>