Skip to content

Multiple labels in CREATE and MATCH #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ private List<Map<String, Object>> submitAndGet(String cypher, Map<String, Object
DefaultGraphTraversal g = new DefaultGraphTraversal(gts);
Translator<GraphTraversal, P> translator = Translator.builder()
.traversal(g)
.enableCypherExtensions()
.build();
CypherAst ast = CypherAst.parse(cypher, parameters);
Seq<GremlinStep> ir = ast.translate(flavor, procedureContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private CypherGremlinClient configuredCypherGremlinClient() {
case "gremlin":
return CypherGremlinClient.translating(gremlinClient, () -> Translator.builder()
.gremlinGroovy()
.allowCypherExtensions()
.enableCypherExtensions()
.build());
case "vanilla":
return CypherGremlinClient.translating(gremlinClient, () -> Translator.builder()
Expand All @@ -86,7 +86,7 @@ private CypherGremlinClient configuredCypherGremlinClient() {
case "bytecode":
return CypherGremlinClient.bytecode(gremlinClient.alias("g"), () -> Translator.builder()
.bytecode()
.allowCypherExtensions()
.enableCypherExtensions()
.build());
case "cosmosdb":
return CypherGremlinClient.translating(gremlinClient, () -> Translator.builder()
Expand All @@ -96,6 +96,7 @@ private CypherGremlinClient configuredCypherGremlinClient() {
return CypherGremlinClient.translating(gremlinClient, () -> Translator.builder()
.gremlinGroovy()
.inlineParameters()
.enableMultipleLabels()
.build(TranslatorFlavor.neptune()));
default:
throw new IllegalArgumentException("Unknown name: " + clientName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,15 @@ private static Translator<String, GroovyPredicate> translatorByName(List<String>
case "cosmosdb":
return builder.build(TranslatorFlavor.cosmosDb());
case "cosmosdb+extensions":
return builder.allowCypherExtensions().build(TranslatorFlavor.cosmosDb());
return builder.enableCypherExtensions().build(TranslatorFlavor.cosmosDb());
case "neptune":
return builder.build(TranslatorFlavor.neptune());
case "neptune+extensions":
return builder.allowCypherExtensions().build(TranslatorFlavor.neptune());
return builder.enableCypherExtensions().build(TranslatorFlavor.neptune());
case "gremlin":
return builder.build(TranslatorFlavor.gremlinServer());
case "gremlin+extensions":
return builder.allowCypherExtensions().build(TranslatorFlavor.gremlinServer());
return builder.enableCypherExtensions().build(TranslatorFlavor.gremlinServer());
case "":
return builder.build(TranslatorFlavor.gremlinServer());
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ private Tokens() {
public static final String START = " cypher.start";
public static final String NULL = " cypher.null";
public static final String UNUSED = " cypher.unused";
public static final String NONEXISTENT = " cypher.nonexistent";
public static final String PATH_EDGE = " cypher.path.edge.";
public static final String PATH_START = " cypher.path.start.";
public static final String MATCH_START = " cypher.match.start.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public CompletableFuture<CypherResultSet> submitAsync(String cypher, Map<String,
}

DefaultGraphTraversal g = new DefaultGraphTraversal(gts.clone());
Translator<GraphTraversal, P> translator = Translator.builder().traversal(g).allowCypherExtensions().build();
Translator<GraphTraversal, P> translator = Translator.builder().traversal(g).enableCypherExtensions().build();
GraphTraversal<?, ?> traversal = ast.buildTranslation(translator);
ReturnNormalizer returnNormalizer = ReturnNormalizer.create(ast.getReturnTypes());
List<Result> results = traversal.toStream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void evalCypher(Context context) throws OpProcessorException {
Translator<String, GroovyPredicate> stringTranslator = Translator.builder()
.gremlinGroovy()
.inlineParameters()
.allowCypherExtensions()
.enableCypherExtensions()
.build();

String gremlin = TranslationWriter.write(ir, stringTranslator, parameters);
Expand All @@ -119,6 +119,7 @@ private void evalCypher(Context context) throws OpProcessorException {

Translator<GraphTraversal, P> traversalTranslator = Translator.builder()
.traversal(g)
.enableCypherExtensions()
.build();

GraphTraversal<?, ?> traversal = TranslationWriter.write(ir, traversalTranslator, parameters);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package org.opencypher.gremlin.translation.translator;


import java.util.EnumSet;
import java.util.Set;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.DefaultGraphTraversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
Expand Down Expand Up @@ -43,18 +45,18 @@ public final class Translator<T, P> {
private final GremlinSteps<T, P> steps;
private final GremlinPredicates<P> predicates;
private final GremlinBindings bindings;
private final boolean cypherExtensions;
private final Set<TranslatorFeature> features;
private final TranslatorFlavor flavor;

private Translator(GremlinSteps<T, P> steps,
GremlinPredicates<P> predicates,
GremlinBindings bindings,
boolean cypherExtensions,
Set<TranslatorFeature> features,
TranslatorFlavor flavor) {
this.steps = steps;
this.predicates = predicates;
this.bindings = bindings;
this.cypherExtensions = cypherExtensions;
this.features = features;
this.flavor = flavor;
}

Expand Down Expand Up @@ -92,12 +94,12 @@ public GremlinBindings bindings() {
}

/**
* Returns true if this translation assumes the CfoG plugin is installed on target server.
* Returns true if a given feature is enabled in this translator.
*
* @return true, if CfoG plugin should be installed, false otherwise
* @return true, if the feature is enabled, false otherwise
*/
public boolean requiresCypherExtensions() {
return cypherExtensions;
public boolean isEnabled(TranslatorFeature feature) {
return features.contains(feature);
}

/**
Expand Down Expand Up @@ -217,7 +219,7 @@ public static class FlavorBuilder<T, P> {
private final GremlinSteps<T, P> steps;
private final GremlinPredicates<P> predicates;
protected GremlinBindings bindings;
protected boolean cypherExtensions = false;
private final Set<TranslatorFeature> features = EnumSet.noneOf(TranslatorFeature.class);

private FlavorBuilder(GremlinSteps<T, P> steps,
GremlinPredicates<P> predicates,
Expand All @@ -228,13 +230,34 @@ private FlavorBuilder(GremlinSteps<T, P> steps,
}

/**
* Builds a {@link Translator} with support for custom functions and predicates
* provided by the CfoG Gremlin Server plugin.
* Enables a feature in the {@link Translator} that's being built.
*
* @return builder for translator
*/
public FlavorBuilder<T, P> allowCypherExtensions() {
cypherExtensions = true;
public FlavorBuilder<T, P> enable(TranslatorFeature feature) {
features.add(feature);
return this;
}

/**
* Enables Cypher extensions in the {@link Translator} that's being built.
*
* @return builder for translator
* @see TranslatorFeature#CYPHER_EXTENSIONS
*/
public FlavorBuilder<T, P> enableCypherExtensions() {
features.add(TranslatorFeature.CYPHER_EXTENSIONS);
return this;
}

/**
* Enables multiple labels translation in the {@link Translator} that's being built.
*
* @return builder for translator
* @see TranslatorFeature#CYPHER_EXTENSIONS
*/
public FlavorBuilder<T, P> enableMultipleLabels() {
features.add(TranslatorFeature.MULTIPLE_LABELS);
return this;
}

Expand All @@ -258,7 +281,7 @@ public Translator<T, P> build(TranslatorFlavor flavor) {
steps,
predicates,
bindings,
cypherExtensions,
features,
flavor != null ? flavor : TranslatorFlavor.gremlinServer()
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2018 "Neo4j, Inc." [https://neo4j.com]
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.opencypher.gremlin.translation.translator;

/**
* Translator features are additional behaviors that can be allowed in translation.
* These need to be enabled individually when creating a {@link Translator}.
*/
public enum TranslatorFeature {
/**
* Support for custom functions and predicates
* provided by the CfoG Gremlin Server plugin.
*/
CYPHER_EXTENSIONS,

/**
* Support for specifying multiple labels for a vertex
* and matching by multiple labels.
*/
MULTIPLE_LABELS
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ import org.opencypher.gremlin.translation.ir.builder.{IRGremlinBindings, IRGreml
import org.opencypher.gremlin.translation.ir.model.GremlinStep
import org.opencypher.gremlin.translation.ir.verify.NoCustomFunctions
import org.opencypher.gremlin.translation.preparser._
import org.opencypher.gremlin.translation.translator.{Translator, TranslatorFlavor}
import org.opencypher.gremlin.translation.translator.TranslatorFeature._
import org.opencypher.gremlin.translation.translator.{Translator, TranslatorFeature, TranslatorFlavor}
import org.opencypher.gremlin.translation.walker.StatementWalker
import org.opencypher.gremlin.traversal.ProcedureContext
import org.opencypher.v9_0.ast._
Expand Down Expand Up @@ -71,7 +72,8 @@ class CypherAst private (
new IRGremlinPredicates,
new IRGremlinBindings
)
.allowCypherExtensions()
.enableCypherExtensions()
.enableMultipleLabels()
.build()

val context = WalkerContext(dsl, expressionTypes, returnTypes, procedures, parameters)
Expand All @@ -97,9 +99,6 @@ class CypherAst private (
*/
def buildTranslation[T, P](dsl: Translator[T, P]): T = {
val ir = translate(dsl.flavor(), ProcedureContext.empty())
if (!dsl.requiresCypherExtensions) {
NoCustomFunctions(ir).foreach(msg => throw new SyntaxException(msg))
}
TranslationWriter.write(ir, dsl, parameters)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ import java.util

import org.apache.tinkerpop.gremlin.process.traversal.Scope
import org.opencypher.gremlin.translation.GremlinSteps
import org.opencypher.gremlin.translation.exception.SyntaxException
import org.opencypher.gremlin.translation.ir.model._
import org.opencypher.gremlin.translation.translator.Translator
import org.opencypher.gremlin.translation.ir.verify._
import org.opencypher.gremlin.translation.translator.{Translator, TranslatorFeature}
import org.opencypher.gremlin.translation.translator.TranslatorFeature._

import scala.collection.JavaConverters._

Expand All @@ -44,7 +47,15 @@ object TranslationWriter {
write(ir, translator, parameters.asScala.toMap)
}

private val postConditions: Map[TranslatorFeature, GremlinPostCondition] = Map(
CYPHER_EXTENSIONS -> NoCustomFunctions,
MULTIPLE_LABELS -> NoMultipleLabels
)

def write[T, P](ir: Seq[GremlinStep], translator: Translator[T, P], parameters: Map[String, Any]): T = {
for ((feature, postCondition) <- postConditions if !translator.isEnabled(feature);
msg <- postCondition(ir)) throw new SyntaxException(msg)

val generator = new TranslationWriter(translator, parameters)
generator.writeSteps(ir, translator.steps())
translator.translate()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,24 @@ object GroupStepFilters extends GremlinRewriter {
// Extracts "has" steps from a list of WHERE expressions
private def whereExtractor(traversals: Seq[Seq[GremlinStep]]): Seq[(String, GremlinStep)] = {
traversals.flatMap {
case SelectK(stepLabel) :: (hasLabel: HasLabel) :: Nil =>
Some((stepLabel, hasLabel))
case SelectK(stepLabel) :: Values(propertyKey) :: Is(predicate) :: Nil =>
Some((stepLabel, HasP(propertyKey, predicate)))
(stepLabel, HasP(propertyKey, predicate)) :: Nil
case SelectK(stepLabel) :: rest if rest.forall(_.isInstanceOf[HasLabel]) =>
rest.map((stepLabel, _))
case _ =>
None
Nil
}
}

// Filters out relocated expressions from WHERE
private def whereFilter(aliases: Set[String])(traversals: Seq[Seq[GremlinStep]]): Option[GremlinStep] = {
val newTraversals = traversals.flatMap {
case SelectK(alias) :: (_: HasLabel) :: Nil if aliases.contains(alias) => None
case SelectK(alias) :: Values(_) :: Is(_) :: Nil if aliases.contains(alias) => None
case other => Some(other)
case SelectK(alias) :: Values(_) :: Is(_) :: Nil if aliases.contains(alias) =>
None
case SelectK(alias) :: rest if aliases.contains(alias) && rest.forall(_.isInstanceOf[HasLabel]) =>
None
case other =>
Some(other)
}.toList
newTraversals match {
case Nil => None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.opencypher.gremlin.translation.ir.rewrite

import org.apache.tinkerpop.gremlin.structure.Column
import org.opencypher.gremlin.translation.Tokens._
import org.opencypher.gremlin.translation.ir.TraversalHelper._
import org.opencypher.gremlin.translation.ir.model.{GremlinStep, _}

Expand All @@ -28,6 +29,7 @@ object NeptuneFlavor extends GremlinRewriter {
injectWorkaround(_),
deleteWorkaround(_),
limit0Workaround(_),
multipleLabelsWorkaround(_),
traversalRewriters(_)
).foldLeft(steps) { (steps, rewriter) =>
rewriter(steps)
Expand Down Expand Up @@ -76,7 +78,16 @@ object NeptuneFlavor extends GremlinRewriter {
private def limit0Workaround(steps: Seq[GremlinStep]): Seq[GremlinStep] = {
replace({
case Barrier :: Limit(0) :: rest =>
SelectK(" cypher.empty.result") :: rest
SelectK(NONEXISTENT) :: rest
})(steps)
}

private def multipleLabelsWorkaround(steps: Seq[GremlinStep]): Seq[GremlinStep] = {
steps match {
case Vertex :: (_: HasLabel) :: (_: HasLabel) :: _ =>
Vertex +: Is(Neq(NONEXISTENT)) +: steps.drop(1)
case _ =>
steps
}
}
}
Loading