Repository: tinkerpop Updated Branches: refs/heads/TINKERPOP-1854 976079d6e -> 93e740a65 (forced update)
TINKERPOP-1854 Add Lambdas for Gremlin.Net All features are passing now (no ignored ones any more). Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/93e740a6 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/93e740a6 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/93e740a6 Branch: refs/heads/TINKERPOP-1854 Commit: 93e740a654b10340d925f6075bc6f672e2e5bc90 Parents: 0bcab7f Author: Florian Hockmann <[email protected]> Authored: Sat Feb 10 15:00:59 2018 +0100 Committer: Florian Hockmann <[email protected]> Committed: Sat Feb 10 15:00:59 2018 +0100 ---------------------------------------------------------------------- docs/src/reference/gremlin-variants.asciidoc | 26 ++++++-- gremlin-dotnet/glv/generate.groovy | 2 +- .../Process/Traversal/GraphTraversal.cs | 10 +-- .../src/Gremlin.Net/Process/Traversal/Lambda.cs | 67 ++++++++++++++++++++ .../src/Gremlin.Net/Process/Traversal/__.cs | 10 +-- .../Structure/IO/GraphSON/GraphSONWriter.cs | 3 +- .../Structure/IO/GraphSON/LambdaSerializer.cs | 45 +++++++++++++ .../Gherkin/CommonSteps.cs | 2 +- .../Gherkin/IgnoreException.cs | 3 - .../TraversalEvaluation/TraversalParser.cs | 2 +- .../IO/GraphSON/GraphSONWriterTests.cs | 13 ++++ 11 files changed, 162 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/docs/src/reference/gremlin-variants.asciidoc ---------------------------------------------------------------------- diff --git a/docs/src/reference/gremlin-variants.asciidoc b/docs/src/reference/gremlin-variants.asciidoc index 4a2fe7a..baa0ec2 100644 --- a/docs/src/reference/gremlin-variants.asciidoc +++ b/docs/src/reference/gremlin-variants.asciidoc @@ -46,7 +46,7 @@ implementation of Gremlin and serves as the foundation by which all other Gremli === The Lambda Solution Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as -most language do not support lambda introspection and thus, code analysis. In Gremlin-Java, Java8 lambdas can be leveraged. +most languages do not support lambda introspection and thus, code analysis. In Gremlin-Java, Java8 lambdas can be leveraged. [source,java] g.V().out("knows").map(t -> t.get().value("name") + " is the friend name") <1> @@ -281,7 +281,7 @@ re-construction machine-side. === The Lambda Solution Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as -most language do not support lambda introspection and thus, code analysis. In Gremlin-Python, +most languages do not support lambda introspection and thus, code analysis. In Gremlin-Python, a link:https://docs.python.org/2/reference/expressions.html#lambda[Python lambda] should be represented as a zero-arg callable that returns a string representation of a lambda. The default lambda language is `gremlin-python` and can be changed via `gremlin_python.statics.default_lambda_language`. When the lambda is represented in `Bytecode` its language is encoded @@ -346,8 +346,10 @@ var g = graph.Traversal().WithRemote(new DriverRemoteConnection(new GremlinClien When a traversal from the `GraphTraversalSource` is iterated, the traversalâs `Bytecode` is sent over the wire via the registered `IRemoteConnection`. The bytecode is used to construct the equivalent traversal at the remote traversal source. -Since Gremlin.Net currently doesn't support lambda expressions, all traversals can be translated to Gremlin-Java on the remote -location (e.g. Gremlin Server). +Moreover, typically the bytecode is analyzed to determine which language the bytecode should be translated to. If the traversal +does not contain lambdas, the remote location (e.g. Gremlin Server) will typically +use Gremlin-Java. If it has lambdas written in Groovy, it will use Gremlin-Groovy (e.g. `GremlinGroovyScriptEngine`). +Likewise, if it has lambdas represented in Python, it will use Gremlin-Python (e.g. `GremlinJythonScriptEngine`). IMPORTANT: Gremlin.Netâs `ITraversal` interface supports the standard Gremlin methods such as `Next()`, `NextTraverser()`, `ToSet()`, `ToList()`, etc. Such "terminal" methods trigger the evaluation of the traversal. @@ -436,6 +438,22 @@ NOTE: Many of the TraversalStrategy classes in Gremlin.Net are proxies to the re JVM-based Gremlin traversal machine. As such, their `Apply(ITraversal)` method does nothing. However, the strategy is encoded in the Gremlin.Net bytecode and transmitted to the Gremlin traversal machine for re-construction machine-side. +=== The Lambda Solution + +Supporting link:https://en.wikipedia.org/wiki/Anonymous_function[anonymous functions] across languages is difficult as +most languages do not support lambda introspection and thus, code analysis. While Gremlin.Net doesn't support C# lambdas, it +is still able to represent lambdas in other languages. When the lambda is represented in `Bytecode` its language is encoded +such that the remote connection host can infer which translator and ultimate execution engine to use. + +[source,csharp] +---- +g.V().Out().Map<int>(Lambda.Groovy("it.get().value('name').length()")).Sum<int>().ToList(); <1> +g.V().Out().Map<int>(Lambda.Python("lambda x: len(x.get().value('name'))")).Sum<int>().ToList(); <2> +---- + +<1> `Lambda.Groovy()` can be used to create a Groovy lambda. +<2> `Lambda.Python()` can be used to create a Python lambda. + [[gremlin-javascript]] == Gremlin-JavaScript http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/glv/generate.groovy ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/glv/generate.groovy b/gremlin-dotnet/glv/generate.groovy index 12cfa88..f1b0ee4 100644 --- a/gremlin-dotnet/glv/generate.groovy +++ b/gremlin-dotnet/glv/generate.groovy @@ -48,7 +48,7 @@ def toCSharpTypeMap = ["Long": "long", "TraversalMetrics": "E2", "Traversal": "ITraversal", "Traversal[]": "ITraversal[]", - "Predicate": "TraversalPredicate", + "Predicate": "object", "P": "TraversalPredicate", "TraversalStrategy": "ITraversalStrategy", "TraversalStrategy[]": "ITraversalStrategy[]", http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs index d8e26ad..1c00f11 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs @@ -355,7 +355,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Adds the choose step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> - public GraphTraversal<S, E2> Choose<E2> (TraversalPredicate choosePredicate, ITraversal trueChoice) + public GraphTraversal<S, E2> Choose<E2> (object choosePredicate, ITraversal trueChoice) { Bytecode.AddStep("choose", choosePredicate, trueChoice); return Wrap<S, E2>(this); @@ -364,7 +364,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Adds the choose step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> - public GraphTraversal<S, E2> Choose<E2> (TraversalPredicate choosePredicate, ITraversal trueChoice, ITraversal falseChoice) + public GraphTraversal<S, E2> Choose<E2> (object choosePredicate, ITraversal trueChoice, ITraversal falseChoice) { Bytecode.AddStep("choose", choosePredicate, trueChoice, falseChoice); return Wrap<S, E2>(this); @@ -496,7 +496,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Adds the emit step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> - public GraphTraversal<S, E> Emit (TraversalPredicate emitPredicate) + public GraphTraversal<S, E> Emit (object emitPredicate) { Bytecode.AddStep("emit", emitPredicate); return Wrap<S, E>(this); @@ -514,7 +514,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Adds the filter step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> - public GraphTraversal<S, E> Filter (TraversalPredicate predicate) + public GraphTraversal<S, E> Filter (object predicate) { Bytecode.AddStep("filter", predicate); return Wrap<S, E>(this); @@ -1591,7 +1591,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Adds the until step to this <see cref="GraphTraversal{SType, EType}" />. /// </summary> - public GraphTraversal<S, E> Until (TraversalPredicate untilPredicate) + public GraphTraversal<S, E> Until (object untilPredicate) { Bytecode.AddStep("until", untilPredicate); return Wrap<S, E>(this); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Lambda.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Lambda.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Lambda.cs new file mode 100644 index 0000000..5709567 --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/Lambda.cs @@ -0,0 +1,67 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +namespace Gremlin.Net.Process.Traversal +{ + /// <summary> + /// Represents a lambda. + /// </summary> + public class Lambda + { + private Lambda(string expression, string language) + { + LambdaExpression = expression; + Language = language; + } + + /// <summary> + /// Gets the lambda expression. + /// </summary> + public string LambdaExpression { get; } + + /// <summary> + /// Gets the language of this lambda. + /// </summary> + public string Language { get; } + + /// <summary> + /// Creates a new Groovy <see cref="Lambda"/>. + /// </summary> + /// <param name="expression">The lambda expression.</param> + /// <returns>The created <see cref="Lambda"/>.</returns> + public static Lambda Groovy(string expression) + { + return new Lambda(expression, "gremlin-groovy"); + } + + /// <summary> + /// Creates a new Python <see cref="Lambda"/>. + /// </summary> + /// <param name="expression">The lambda expression.</param> + /// <returns>The created <see cref="Lambda"/>.</returns> + public static Lambda Python(string expression) + { + return new Lambda(expression, "gremlin-python"); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs index 1788bad..9c186ee 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs @@ -231,7 +231,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the choose step to that traversal. /// </summary> - public static GraphTraversal<object, E2> Choose<E2>(TraversalPredicate choosePredicate, ITraversal trueChoice) + public static GraphTraversal<object, E2> Choose<E2>(object choosePredicate, ITraversal trueChoice) { return new GraphTraversal<object, E2>().Choose<E2>(choosePredicate, trueChoice); } @@ -239,7 +239,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the choose step to that traversal. /// </summary> - public static GraphTraversal<object, E2> Choose<E2>(TraversalPredicate choosePredicate, ITraversal trueChoice, ITraversal falseChoice) + public static GraphTraversal<object, E2> Choose<E2>(object choosePredicate, ITraversal trueChoice, ITraversal falseChoice) { return new GraphTraversal<object, E2>().Choose<E2>(choosePredicate, trueChoice, falseChoice); } @@ -357,7 +357,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the emit step to that traversal. /// </summary> - public static GraphTraversal<object, object> Emit(TraversalPredicate emitPredicate) + public static GraphTraversal<object, object> Emit(object emitPredicate) { return new GraphTraversal<object, object>().Emit(emitPredicate); } @@ -373,7 +373,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the filter step to that traversal. /// </summary> - public static GraphTraversal<object, object> Filter(TraversalPredicate predicate) + public static GraphTraversal<object, object> Filter(object predicate) { return new GraphTraversal<object, object>().Filter(predicate); } @@ -1239,7 +1239,7 @@ namespace Gremlin.Net.Process.Traversal /// <summary> /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the until step to that traversal. /// </summary> - public static GraphTraversal<object, object> Until(TraversalPredicate untilPredicate) + public static GraphTraversal<object, object> Until(object untilPredicate) { return new GraphTraversal<object, object>().Until(untilPredicate); } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs index a60a558..e4a2999 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONWriter.cs @@ -59,7 +59,8 @@ namespace Gremlin.Net.Structure.IO.GraphSON {typeof(Edge), new EdgeSerializer()}, {typeof(Property), new PropertySerializer()}, {typeof(VertexProperty), new VertexPropertySerializer()}, - {typeof(AbstractTraversalStrategy), new TraversalStrategySerializer()} + {typeof(AbstractTraversalStrategy), new TraversalStrategySerializer()}, + {typeof(Lambda), new LambdaSerializer()} }; /// <summary> http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/LambdaSerializer.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/LambdaSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/LambdaSerializer.cs new file mode 100644 index 0000000..8e712b1 --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/LambdaSerializer.cs @@ -0,0 +1,45 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using System.Collections.Generic; +using Gremlin.Net.Process.Traversal; + +namespace Gremlin.Net.Structure.IO.GraphSON +{ + internal class LambdaSerializer : IGraphSONSerializer + { + private const int DefaultArgument = -1; + + public Dictionary<string, dynamic> Dictify(dynamic objectData, GraphSONWriter writer) + { + Lambda lambda = objectData; + var valueDict = new Dictionary<string, dynamic> + { + {"script", lambda.LambdaExpression}, + {"language", lambda.Language}, + {"arguments", DefaultArgument} + }; + return GraphSONUtil.ToTypedValue(nameof(Lambda), valueDict); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs index 0abc247..dd96474 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs @@ -233,7 +233,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin private static object ToLambda(string stringLambda, string graphName) { - throw new IgnoreException(IgnoreReason.LambdaNotSupported); + return Lambda.Groovy(stringLambda); } private static object ToNumber(string stringNumber, string graphName) http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs index ae236c7..d3c7242 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/IgnoreException.cs @@ -40,9 +40,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin string reasonSuffix = null; switch (reason) { - case IgnoreReason.LambdaNotSupported: - reasonSuffix = " because lambdas are not supported in Gremlin.NET"; - break; } return $"Scenario ignored" + reasonSuffix; } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs index 118fcea..aeef107 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalEvaluation/TraversalParser.cs @@ -398,7 +398,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin.TraversalEvaluation { return ParseNumber(text, ref i); } - if (text.Substring(i, 3).StartsWith("__.")) + if (text.Length >= i + 3 && text.Substring(i, 3).StartsWith("__.")) { var startIndex = i; var tokens = ParseTokens(text, ref i); http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/93e740a6/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs ---------------------------------------------------------------------- diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs index 3d02533..7774907 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs @@ -333,6 +333,19 @@ namespace Gremlin.Net.UnitTest.Structure.IO.GraphSON const string expected = "{\"@type\":\"g:SubgraphStrategy\",\"@value\":{}}"; Assert.Equal(expected, graphSon); } + + [Fact] + public void ShouldSerializeLambda() + { + var writer = CreateStandardGraphSONWriter(); + var lambda = Lambda.Groovy("{ it.get() }"); + + var graphSon = writer.WriteObject(lambda); + + const string expected = + "{\"@type\":\"g:Lambda\",\"@value\":{\"script\":\"{ it.get() }\",\"language\":\"gremlin-groovy\",\"arguments\":-1}}"; + Assert.Equal(expected, graphSon); + } } internal class TestGraphSONSerializer : IGraphSONSerializer
