[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>