This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-2002 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/TINKERPOP-2002 by this push: new ec661c4 TINKERPOP-2002 Moved DSL specifics to each language variant ec661c4 is described below commit ec661c42195dd3f55843b40a048a1e7f0f9af4aa Author: Stephen Mallette <sp...@genoprime.com> AuthorDate: Fri Nov 9 15:13:48 2018 -0500 TINKERPOP-2002 Moved DSL specifics to each language variant --- docs/src/reference/gremlin-variants.asciidoc | 291 ++++++++++++++++++++++++++ docs/src/reference/the-traversal.asciidoc | 294 +-------------------------- 2 files changed, 293 insertions(+), 292 deletions(-) diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 0eb8885..d4074c6 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -418,6 +418,132 @@ g2Client.submit("g.V()") The above code demonstrates how the `alias` method can be used such that the script need only contain a reference to "g" and "g1" and "g2" are automatically rebound into "g" on the server-side. +[[gremlin-java-dsl]] +=== Domain Specific Languages + +Creating a <<dsl,Domain Specific Language>> (DSL) in Java requires the `@GremlinDsl` Java annotation in `gremlin-core`. This +annotation should be applied to a "DSL interface" that extends `GraphTraversal.Admin`. + +[source,java] +---- +@GremlinDsl +public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { +} +---- + +IMPORTANT: The name of the DSL interface should be suffixed with "TraversalDSL". All characters in the interface name +before that become the "name" of the DSL. + +In this interface, define the methods that the DSL will be composed of: + +[source,java] +---- +@GremlinDsl +public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { + public default GraphTraversal<S, Vertex> knows(String personName) { + return out("knows").hasLabel("person").has("name", personName); + } + + public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() { + return out("knows").hasLabel("person").values("age").min(); + } + + public default GraphTraversal<S, Long> createdAtLeast(int number) { + return outE("created").count().is(P.gte(number)); + } +} +---- + +IMPORTANT: Follow the TinkerPop convention of using `<S,E>` in naming generics as those conventions are taken into +account when generating the anonymous traversal class. The processor attempts to infer the appropriate type parameters +when generating the anonymous traversal class. If it cannot do it correctly, it is possible to avoid the inference by +using the `GremlinDsl.AnonymousMethod` annotation on the DSL method. It allows explicit specification of the types to +use. + +The `@GremlinDsl` annotation is used by the link:https://docs.oracle.com/javase/8/docs/api/index.html?javax/annotation/processing/Processor.html[Java Annotation Processor] +to generate the boilerplate class structure required to properly use the DSL within the TinkerPop framework. These +classes can be generated and maintained by hand, but it would be time consuming, monotonous and error-prone to do so. +Typically, the Java compilation process is automatically configured to detect annotation processors on the classpath +and will automatically use them when found. If that does not happen, it may be necessary to make configuration changes +to the build to allow for the compilation process to be aware of the following `javax.annotation.processing.Processor` +implementation: + +[source,java] +---- +org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor +---- + +The annotation processor will generate several classes for the DSL: + +* `SocialTraversal` - A `Traversal` interface that extends the `SocialTraversalDsl` proxying methods to its underlying +interfaces (such as `GraphTraversal`) to instead return a `SocialTraversal` +* `DefaultSocialTraversal` - A default implementation of `SocialTraversal` (typically not used directly by the user) +* `SocialTraversalSource` - Spawns `DefaultSocialTraversal` instances. +* `__` - Spawns anonymous `DefaultSocialTraversal` instances. + +Using the DSL then just involves telling the `Graph` to use it: + +[source,java] +---- +SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); +social.V().has("name","marko").knows("josh"); +---- + +The `SocialTraversalSource` can also be customized with DSL functions. As an additional step, include a class that +extends from `GraphTraversalSource` and with a name that is suffixed with "TraversalSourceDsl". Include in this class, +any custom methods required by the DSL: + +[source,java] +---- +public class SocialTraversalSourceDsl extends GraphTraversalSource { + + public SocialTraversalSourceDsl(Graph graph, TraversalStrategies traversalStrategies) { + super(graph, traversalStrategies); + } + + public SocialTraversalSourceDsl(Graph graph) { + super(graph); + } + + public GraphTraversal<Vertex, Vertex> persons(String... names) { + GraphTraversalSource clone = this.clone(); + + // Manually add a "start" step for the traversal in this case the equivalent of V(). GraphStep is marked + // as a "start" step by passing "true" in the constructor. + clone.getBytecode().addStep(GraphTraversal.Symbols.V); + GraphTraversal<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); + traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Vertex.class, true)); + + traversal = traversal.hasLabel("person"); + if (names.length > 0) traversal = traversal.has("name", P.within(names)); + + return traversal; + } +} +---- + +Then, back in the `SocialTraversal` interface, update the `GremlinDsl` annotation with the `traversalSource` argument +to point to the fully qualified class name of the `SocialTraversalSourceDsl`: + +[source,java] +---- +@GremlinDsl(traversalSource = "com.company.SocialTraversalSourceDsl") +public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { + ... +} +---- + +It is then possible to use the `persons()` method to start traversals: + +[source,java] +---- +SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); +social.persons("marko").knows("josh"); +---- + +NOTE: Using Maven, as shown in the `gremlin-archetype-dsl` module, makes developing DSLs with the annotation processor +straightforward in that it sets up appropriate paths to the generated code automatically. + [[gremlin-groovy]] == Gremlin-Groovy @@ -736,6 +862,78 @@ returns a `concurrent.futures.Future` that resolves to a list when it is complet <9> Verify that the all results have been read and stream is closed. <10> Close client and underlying pool connections. +=== Domain Specific Languages + +Writing a Gremlin <<dsl,Domain Specific Language>> (DSL) in Python simply requires direct extension of several classes: + +* `GraphTraversal` - which exposes the various steps used in traversal writing +* `__` - which spawns anonymous traversals from steps +* `GraphTraversalSource` - which spawns `GraphTraversal` instances + +The Social DSL based on the link:http://tinkerpop.apache.org/docs/current/images/tinkerpop-modern.png["modern" toy graph] +might look like this: + +[source,python] +---- +class SocialTraversal(GraphTraversal): + + def knows(self, person_name): + return self.out("knows").hasLabel("person").has("name", person_name) + + def youngestFriendsAge(self): + return self.out("knows").hasLabel("person").values("age").min() + + def createdAtLeast(self, number): + return self.outE("created").count().is_(P.gte(number)) + +class __(AnonymousTraversal): + + graph_traversal = SocialTraversal + + @classmethod + def knows(cls, *args): + return cls.graph_traversal(None, None, Bytecode()).knows(*args) + + @classmethod + def youngestFriendsAge(cls, *args): + return cls.graph_traversal(None, None, Bytecode()).youngestFriendsAge(*args) + + @classmethod + def createdAtLeast(cls, *args): + return cls.graph_traversal(None, None, Bytecode()).createdAtLeast(*args) + + +class SocialTraversalSource(GraphTraversalSource): + + def __init__(self, *args, **kwargs): + super(SocialTraversalSource, self).__init__(*args, **kwargs) + self.graph_traversal = SocialTraversal + + def persons(self, *args): + traversal = self.get_graph_traversal() + traversal.bytecode.add_step("V") + traversal.bytecode.add_step("hasLabel", "person") + + if len(args) > 0: + traversal.bytecode.add_step("has", "name", P.within(args)) + + return traversal +---- + +NOTE: The `AnonymousTraversal` class above is just an alias for `__` as in +`from gremlin_python.process.graph_traversal import __ as AnonymousTraversal` + +Using the DSL is straightforward and just requires that the graph instance know the `SocialTraversalSource` should +be used: + +[source,python] +---- +social = Graph().traversal(SocialTraversalSource).withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g')) +social.persons("marko").knows("josh") +social.persons("marko").youngestFriendsAge() +social.persons().filter(__.createdAtLeast(2)).count() +---- + [[gremlin-DotNet]] == Gremlin.Net @@ -890,6 +1088,99 @@ g.V().Out().Map<int>(Lambda.Python("lambda x: len(x.get().value('name'))")).Sum< The `ILambda` interface returned by these two methods inherits interfaces like `IFunction` and `IPredicate` that mirror their Java counterparts which makes it possible to use lambdas with Gremlin.Net for the same steps as in Gremlin-Java. +=== Domain Specific Languages + +Developing a <<dsl,Domain Specific Language>> (DSL) for .Net is most easily implemented using +link:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods[Extension Methods] +as they don't require direct extension of classes in the TinkerPop hierarchy. Extension Method classes simply need to +be constructed for the `GraphTraversal` and the `GraphTraversalSource`. Unfortunately, anonymous traversals (spawned +from `__`) can't use the Extension Method approach as they do not work for static classes and static classes can't be +extended. The only option is to re-implement the methods of `__` as a wrapper in the anonymous traversal for the DSL +or to simply create a static class for the DSL and use the two anonymous traversals creators independently. The +following example uses the latter approach as it saves a lot of boilerplate code with the minor annoyance of having a +second static class to deal with when writing traversals rather than just calling `__` for everything. + +[source,csharp] +---- +namespace Dsl +{ + + public static class SocialTraversalExtensions + { + public static GraphTraversal<Vertex,Vertex> Knows(this GraphTraversal<Vertex,Vertex> t, string personName) + { + return t.Out("knows").HasLabel("person").Has("name", personName); + } + + public static GraphTraversal<Vertex, int> YoungestFriendsAge(this GraphTraversal<Vertex,Vertex> t) + { + return t.Out("knows").HasLabel("person").Values<int>("age").Min<int>(); + } + + public static GraphTraversal<Vertex,long> CreatedAtLeast(this GraphTraversal<Vertex,Vertex> t, long number) + { + return t.OutE("created").Count().Is(P.Gte(number)); + } + } + + public static class __Social + { + public static GraphTraversal<object,Vertex> Knows(string personName) + { + return __.Out("knows").HasLabel("person").Has("name", personName); + } + + public static GraphTraversal<object, int> YoungestFriendsAge() + { + return __.Out("knows").HasLabel("person").Values<int>("age").Min<int>(); + } + + public static GraphTraversal<object,long> CreatedAtLeast(long number) + { + return __.OutE("created").Count().Is(P.Gte(number)); + } + } + + public static class SocialTraversalSourceExtensions + { + public static GraphTraversal<Vertex,Vertex> Persons(this GraphTraversalSource g, params string[] personNames) + { + GraphTraversal<Vertex,Vertex> t = g.V().HasLabel("person"); + + if (personNames.Length > 0) + { + t = t.Has("name", P.Within(personNames)); + } + + return t; + } + } +} +---- + +Note the creation of `__Social` as the Social DSL's "extension" to the available ways in which to spawn anonymous +traversals. The use of the double underscore prefix in the name is just a convention to consider using and is not a +requirement. To use the DSL, bring it into scope with the `using` directive: + +[source,csharp] +---- +using Dsl; +using static Dsl.__Social; +---- + +and then it can be called from the application as follows: + +[source,csharp] +---- +var graph = new Graph(); +var connection = new DriverRemoteConnection(new GremlinClient(new GremlinServer("localhost", 8182))); +var social = graph.Traversal().WithRemote(connection); + +social.Persons("marko").Knows("josh"); +social.Persons("marko").YoungestFriendsAge(); +social.Persons().Filter(CreatedAtLeast(2)).Count(); +---- + [[gremlin-javascript]] == Gremlin-JavaScript diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index d2243d9..d46e072 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -3931,295 +3931,5 @@ g.V().hasLabel('person').has('name','marko'). social.persons("marko").youngestFriendsAge() ---- -The following sections explain how to develop application specific DSLs for different <<gremlin-variants,Gremlin Language Variants>> -using the examples above of the Social DSL as the API for the implementation. - -[[gremlin-java-dsl]] -=== Gremlin-Java - -Creating a DSL in Java requires the `@GremlinDsl` Java annotation in `gremlin-core`. This annotation should be applied -to a "DSL interface" that extends `GraphTraversal.Admin`. - -[source,java] ----- -@GremlinDsl -public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { -} ----- - -IMPORTANT: The name of the DSL interface should be suffixed with "TraversalDSL". All characters in the interface name -before that become the "name" of the DSL. - -In this interface, define the methods that the DSL will be composed of: - -[source,java] ----- -@GremlinDsl -public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { - public default GraphTraversal<S, Vertex> knows(String personName) { - return out("knows").hasLabel("person").has("name", personName); - } - - public default <E2 extends Number> GraphTraversal<S, E2> youngestFriendsAge() { - return out("knows").hasLabel("person").values("age").min(); - } - - public default GraphTraversal<S, Long> createdAtLeast(int number) { - return outE("created").count().is(P.gte(number)); - } -} ----- - -IMPORTANT: Follow the TinkerPop convention of using `<S,E>` in naming generics as those conventions are taken into -account when generating the anonymous traversal class. The processor attempts to infer the appropriate type parameters -when generating the anonymous traversal class. If it cannot do it correctly, it is possible to avoid the inference by -using the `GremlinDsl.AnonymousMethod` annotation on the DSL method. It allows explicit specification of the types to -use. - -The `@GremlinDsl` annotation is used by the link:https://docs.oracle.com/javase/8/docs/api/index.html?javax/annotation/processing/Processor.html[Java Annotation Processor] -to generate the boilerplate class structure required to properly use the DSL within the TinkerPop framework. These -classes can be generated and maintained by hand, but it would be time consuming, monotonous and error-prone to do so. -Typically, the Java compilation process is automatically configured to detect annotation processors on the classpath -and will automatically use them when found. If that does not happen, it may be necessary to make configuration changes -to the build to allow for the compilation process to be aware of the following `javax.annotation.processing.Processor` -implementation: - -[source,java] ----- -org.apache.tinkerpop.gremlin.process.traversal.dsl.GremlinDslProcessor ----- - -The annotation processor will generate several classes for the DSL: - -* `SocialTraversal` - A `Traversal` interface that extends the `SocialTraversalDsl` proxying methods to its underlying -interfaces (such as `GraphTraversal`) to instead return a `SocialTraversal` -* `DefaultSocialTraversal` - A default implementation of `SocialTraversal` (typically not used directly by the user) -* `SocialTraversalSource` - Spawns `DefaultSocialTraversal` instances. -* `__` - Spawns anonymous `DefaultSocialTraversal` instances. - -Using the DSL then just involves telling the `Graph` to use it: - -[source,java] ----- -SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); -social.V().has("name","marko").knows("josh"); ----- - -The `SocialTraversalSource` can also be customized with DSL functions. As an additional step, include a class that -extends from `GraphTraversalSource` and with a name that is suffixed with "TraversalSourceDsl". Include in this class, -any custom methods required by the DSL: - -[source,java] ----- -public class SocialTraversalSourceDsl extends GraphTraversalSource { - - public SocialTraversalSourceDsl(Graph graph, TraversalStrategies traversalStrategies) { - super(graph, traversalStrategies); - } - - public SocialTraversalSourceDsl(Graph graph) { - super(graph); - } - - public GraphTraversal<Vertex, Vertex> persons(String... names) { - GraphTraversalSource clone = this.clone(); - - // Manually add a "start" step for the traversal in this case the equivalent of V(). GraphStep is marked - // as a "start" step by passing "true" in the constructor. - clone.getBytecode().addStep(GraphTraversal.Symbols.V); - GraphTraversal<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); - traversal.asAdmin().addStep(new GraphStep<>(traversal.asAdmin(), Vertex.class, true)); - - traversal = traversal.hasLabel("person"); - if (names.length > 0) traversal = traversal.has("name", P.within(names)); - - return traversal; - } -} ----- - -Then, back in the `SocialTraversal` interface, update the `GremlinDsl` annotation with the `traversalSource` argument -to point to the fully qualified class name of the `SocialTraversalSourceDsl`: - -[source,java] ----- -@GremlinDsl(traversalSource = "com.company.SocialTraversalSourceDsl") -public interface SocialTraversalDsl<S, E> extends GraphTraversal.Admin<S, E> { - ... -} ----- - -It is then possible to use the `persons()` method to start traversals: - -[source,java] ----- -SocialTraversalSource social = graph.traversal(SocialTraversalSource.class); -social.persons("marko").knows("josh"); ----- - -NOTE: Using Maven, as shown in the `gremlin-archetype-dsl` module, makes developing DSLs with the annotation processor -straightforward in that it sets up appropriate paths to the generated code automatically. - -=== Gremlin-Python - -Writing a Gremlin DSL in Python simply requires direct extension of several classes: - -* `GraphTraversal` - which exposes the various steps used in traversal writing -* `__` - which spawns anonymous traversals from steps -* `GraphTraversalSource` - which spawns `GraphTraversal` instances - -The Social DSL based on the link:http://tinkerpop.apache.org/docs/current/images/tinkerpop-modern.png["modern" toy graph] -might look like this: - -[source,python] ----- -class SocialTraversal(GraphTraversal): - - def knows(self, person_name): - return self.out("knows").hasLabel("person").has("name", person_name) - - def youngestFriendsAge(self): - return self.out("knows").hasLabel("person").values("age").min() - - def createdAtLeast(self, number): - return self.outE("created").count().is_(P.gte(number)) - -class __(AnonymousTraversal): - - graph_traversal = SocialTraversal - - @classmethod - def knows(cls, *args): - return cls.graph_traversal(None, None, Bytecode()).knows(*args) - - @classmethod - def youngestFriendsAge(cls, *args): - return cls.graph_traversal(None, None, Bytecode()).youngestFriendsAge(*args) - - @classmethod - def createdAtLeast(cls, *args): - return cls.graph_traversal(None, None, Bytecode()).createdAtLeast(*args) - - -class SocialTraversalSource(GraphTraversalSource): - - def __init__(self, *args, **kwargs): - super(SocialTraversalSource, self).__init__(*args, **kwargs) - self.graph_traversal = SocialTraversal - - def persons(self, *args): - traversal = self.get_graph_traversal() - traversal.bytecode.add_step("V") - traversal.bytecode.add_step("hasLabel", "person") - - if len(args) > 0: - traversal.bytecode.add_step("has", "name", P.within(args)) - - return traversal ----- - -NOTE: The `AnonymousTraversal` class above is just an alias for `__` as in -`from gremlin_python.process.graph_traversal import __ as AnonymousTraversal` - -Using the DSL is straightforward and just requires that the graph instance know the `SocialTraversalSource` should -be used: - -[source,python] ----- -social = Graph().traversal(SocialTraversalSource).withRemote(DriverRemoteConnection('ws://localhost:8182/gremlin','g')) -social.persons("marko").knows("josh") -social.persons("marko").youngestFriendsAge() -social.persons().filter(__.createdAtLeast(2)).count() ----- - -=== Gremlin.Net - -Developing DSLs for .Net is most easily implemented using link:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods[Extension Methods] -as they don't require direct extension of classes in the TinkerPop hierarchy. Extension Method classes simply need to -be constructed for the `GraphTraversal` and the `GraphTraversalSource`. Unfortunately, anonymous traversals (spawned -from `__`) can't use the Extension Method approach as they do not work for static classes and static classes can't be -extended. The only option is to re-implement the methods of `__` as a wrapper in the anonymous traversal for the DSL -or to simply create a static class for the DSL and use the two anonymous traversals creators independently. The -following example uses the latter approach as it saves a lot of boilerplate code with the minor annoyance of having a -second static class to deal with when writing traversals rather than just calling `__` for everything. - -[source,csharp] ----- -namespace Dsl -{ - - public static class SocialTraversalExtensions - { - public static GraphTraversal<Vertex,Vertex> Knows(this GraphTraversal<Vertex,Vertex> t, string personName) - { - return t.Out("knows").HasLabel("person").Has("name", personName); - } - - public static GraphTraversal<Vertex, int> YoungestFriendsAge(this GraphTraversal<Vertex,Vertex> t) - { - return t.Out("knows").HasLabel("person").Values<int>("age").Min<int>(); - } - - public static GraphTraversal<Vertex,long> CreatedAtLeast(this GraphTraversal<Vertex,Vertex> t, long number) - { - return t.OutE("created").Count().Is(P.Gte(number)); - } - } - - public static class __Social - { - public static GraphTraversal<object,Vertex> Knows(string personName) - { - return __.Out("knows").HasLabel("person").Has("name", personName); - } - - public static GraphTraversal<object, int> YoungestFriendsAge() - { - return __.Out("knows").HasLabel("person").Values<int>("age").Min<int>(); - } - - public static GraphTraversal<object,long> CreatedAtLeast(long number) - { - return __.OutE("created").Count().Is(P.Gte(number)); - } - } - - public static class SocialTraversalSourceExtensions - { - public static GraphTraversal<Vertex,Vertex> Persons(this GraphTraversalSource g, params string[] personNames) - { - GraphTraversal<Vertex,Vertex> t = g.V().HasLabel("person"); - - if (personNames.Length > 0) - { - t = t.Has("name", P.Within(personNames)); - } - - return t; - } - } -} ----- - -Note the creation of `__Social` as the Social DSL's "extension" to the available ways in which to spawn anonymous -traversals. The use of the double underscore prefix in the name is just a convention to consider using and is not a -requirement. To use the DSL, bring it into scope with the `using` directive: - -[source,csharp] ----- -using Dsl; -using static Dsl.__Social; ----- - -and then it can be called from the application as follows: - -[source,csharp] ----- -var graph = new Graph(); -var connection = new DriverRemoteConnection(new GremlinClient(new GremlinServer("localhost", 8182))); -var social = graph.Traversal().WithRemote(connection); - -social.Persons("marko").Knows("josh"); -social.Persons("marko").YoungestFriendsAge(); -social.Persons().Filter(CreatedAtLeast(2)).Count(); ----- +Learn more about hwo to implement these DSLs in the <<gremlin-drivers-variants,Gremlin Language Variants>> section +specific to the programming language of interest.