This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-3028 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 826b945e03f75157d7560a6bced4a738bbe876f4 Author: Stephen Mallette <[email protected]> AuthorDate: Wed Jan 3 08:32:02 2024 -0500 groovy --- .../translator/GroovyTranslateVisitor.java | 116 +++++++++++++++++++ .../language/translator/JavaTranslateVisitor.java | 2 + .../translator/JavascriptTranslateVisitor.java | 10 ++ .../translator/PythonTranslateVisitor.java | 1 + .../language/translator/TranslateVisitor.java | 5 +- .../gremlin/language/translator/Translator.java | 5 + .../language/translator/GremlinTranslatorTest.java | 128 +++++++++++++++++++-- .../tinkerpop/gremlin/features/StepDefinition.java | 8 +- 8 files changed, 262 insertions(+), 13 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java new file mode 100644 index 0000000000..44e7d2d56f --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java @@ -0,0 +1,116 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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.tinkerpop.gremlin.language.translator; + +import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; + +/** + * Converts a Gremlin traversal string into a Groovy source code representation of that traversal with an aim at + * sacrificing some formatting for the ability to compile correctly. The translations may require use of TinkerPop's + * sugar syntax and therefore requires use of the {@code GremlinLoader} in the gremlin-groovy module unless you are + * specifically certain that your translations will not result in the use of that syntax. If in doubt, prefer the + * {@link JavaTranslateVisitor} instead. + * <ul> + * <li>Normalize numeric suffixes to lower case</li> + * <li>If floats are not suffixed they will translate as BigDecimal</li> + * <li>Makes anonymous traversals explicit with double underscore</li> + * <li>Makes enums explicit with their proper name</li> + * </ul> + */ +public class GroovyTranslateVisitor extends TranslateVisitor { + public GroovyTranslateVisitor() { + this("g"); + } + + public GroovyTranslateVisitor(final String graphTraversalSourceName) { + super(graphTraversalSourceName); + } + + @Override + public Void visitStructureVertex(final GremlinParser.StructureVertexContext ctx) { + sb.append("new DetachedVertex("); + visit(ctx.getChild(3)); // id + sb.append(", "); + visit(ctx.getChild(5)); // label + sb.append(")"); + return null; + } + + @Override + public Void visitIntegerLiteral(final GremlinParser.IntegerLiteralContext ctx) { + final String integerLiteral = ctx.getText().toLowerCase(); + + // check suffix + final int lastCharIndex = integerLiteral.length() - 1; + final char lastCharacter = integerLiteral.charAt(lastCharIndex); + switch (lastCharacter) { + case 'b': + // parse B/b as byte + sb.append("new Byte("); + sb.append(integerLiteral, 0, lastCharIndex); + sb.append(")"); + break; + case 's': + // parse S/s as short + sb.append("new Short("); + sb.append(integerLiteral, 0, lastCharIndex); + sb.append(")"); + break; + case 'i': + case 'l': + // parse I/i and L/l as Integer and Long respectively + sb.append(integerLiteral, 0, lastCharIndex).append(lastCharacter); + break; + case 'n': + // parse N/n as BigInteger which for groovy is "g" shorthand + sb.append(integerLiteral, 0, lastCharIndex).append("g"); + break; + default: + // everything else just goes as specified + sb.append(integerLiteral); + break; + } + return null; + } + + @Override + public Void visitFloatLiteral(final GremlinParser.FloatLiteralContext ctx) { + final String floatLiteral = ctx.getText().toLowerCase(); + + // check suffix + final int lastCharIndex = floatLiteral.length() - 1; + final char lastCharacter = floatLiteral.charAt(lastCharIndex); + switch (lastCharacter) { + case 'f': + case 'd': + // parse F/f as Float and D/d suffix as Double + sb.append(floatLiteral, 0, lastCharIndex).append(lastCharacter); + break; + case 'm': + // parse N/n as BigDecimal which for groovy is "g" shorthand + sb.append(floatLiteral, 0, lastCharIndex).append("g"); + break; + default: + // everything else just goes as specified + sb.append(floatLiteral); + break; + } + return null; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java index f176e85943..9f0d43065e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java @@ -31,6 +31,8 @@ import java.util.List; * sacrificing some formatting for the ability to compile correctly. * <ul> * <li>Range syntax has no direct support</li> + * <li>Normalizes whitespace</li> + * <li>Normalize numeric suffixes to lower case</li> * <li>If floats are not suffixed they will translate as BigDecimal</li> * <li>Makes anonymous traversals explicit with double underscore</li> * <li>Makes enums explicit with their proper name</li> diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavascriptTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavascriptTranslateVisitor.java index f6d03478a0..b0f0852dd2 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavascriptTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavascriptTranslateVisitor.java @@ -30,6 +30,16 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Converts a Gremlin traversal string into a Javascript source code representation of that traversal with an aim at + * sacrificing some formatting for the ability to compile correctly. + * <ul> + * <li>Range syntax has no direct support</li> + * <li>Normalizes whitespace</li> + * <li>Makes anonymous traversals explicit with double underscore</li> + * <li>Makes enums explicit with their proper name</li> + * </ul> + */ public class JavascriptTranslateVisitor extends AbstractTranslateVisitor { public JavascriptTranslateVisitor() { this("g"); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/PythonTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/PythonTranslateVisitor.java index 76a40010c6..4a99dade0c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/PythonTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/PythonTranslateVisitor.java @@ -35,6 +35,7 @@ import java.util.Map; * sacrificing some formatting for the ability to compile correctly. * <ul> * <li>Range syntax has no direct support</li> + * <li>Normalizes whitespace</li> * <li>If floats are not suffixed they will translate as BigDecimal</li> * <li>Makes anonymous traversals explicit with double underscore</li> * <li>Makes enums explicit with their proper name</li> diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java index 924011c734..94a4bb7632 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java @@ -44,6 +44,7 @@ import java.util.Set; * A Gremlin to Gremlin translator. Makes no changes to input except: * <ul> * <li>Normalizes whitespace</li> + * <li>Normalize numeric suffixes to lower case</li> * <li>Makes anonymous traversals explicit with double underscore</li> * <li>Makes enums explicit with their proper name</li> * </ul> @@ -2269,13 +2270,13 @@ public class TranslateVisitor extends AbstractParseTreeVisitor<Void> implements @Override public Void visitIntegerLiteral(final GremlinParser.IntegerLiteralContext ctx) { - sb.append(ctx.getText()); + sb.append(ctx.getText().toLowerCase()); return null; } @Override public Void visitFloatLiteral(final GremlinParser.FloatLiteralContext ctx) { - sb.append(ctx.getText()); + sb.append(ctx.getText().toLowerCase()); return null; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/Translator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/Translator.java index 169105c2bf..9a1788b9f5 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/Translator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/Translator.java @@ -35,6 +35,11 @@ public enum Translator { */ ANONYMIZED("Anonymized", AnonymizedTranslatorVisitor::new), + /** + * Translates to gremlin-groovy. + */ + GROOVY("Groovy", GroovyTranslateVisitor::new), + /** * Translates to gremlin-java. */ diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/translator/GremlinTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/translator/GremlinTranslatorTest.java index ce95ec4c9d..a9f954d414 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/translator/GremlinTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/translator/GremlinTranslatorTest.java @@ -80,6 +80,7 @@ public class GremlinTranslatorTest { private final String query; private final String expectedForLang; private final String expectedForAnonymized; + private final String expectedForGroovy; private final String expectedForJava; private final String expectedForJavascript; private final String expectedForPython; @@ -90,6 +91,7 @@ public class GremlinTranslatorTest { * <ol> * <li>Language</li> * <li>Anonymized</li> + * <li>Groovy</li> * <li>Java</li> * <li>Javascript</li> * <li>Python</li> @@ -104,256 +106,306 @@ public class GremlinTranslatorTest { null, null, null, + null, null}, {"g.with(\"x\")", null, "g.with(string0)", null, + null, "g.with_(\"x\")", "g.with_('x')"}, {"g.with(\"x\n\\\"yz\")", null, "g.with(string0)", null, + null, "g.with_(\"x\n\\\"yz\")", "g.with_('x\n\\\"yz')"}, {"g.with('x', 'xyz')", null, "g.with(string0, string1)", + null, "g.with(\"x\", \"xyz\")", "g.with_(\"x\", \"xyz\")", "g.with_('x', 'xyz')"}, {"g.with('x','xyz')", "g.with('x', 'xyz')", "g.with(string0, string1)", + "g.with('x', 'xyz')", "g.with(\"x\", \"xyz\")", "g.with_(\"x\", \"xyz\")", "g.with_('x', 'xyz')"}, {"g.with('x', '')", null, "g.with(string0, string1)", + null, "g.with(\"x\", \"\")", "g.with_(\"x\", \"\")", "g.with_('x', '')"}, + {"g.with('x', ' ')", + null, + "g.with(string0, string1)", + null, + "g.with(\"x\", \" \")", + "g.with_(\"x\", \" \")", + "g.with_('x', ' ')"}, {"g.with('x', 'x')", null, "g.with(string0, string0)", + null, "g.with(\"x\", \"x\")", "g.with_(\"x\", \"x\")", "g.with_('x', 'x')"}, {"g.with('x', null)", null, "g.with(string0, object0)", + null, "g.with(\"x\", null)", "g.with_(\"x\", null)", "g.with_('x', None)"}, {"g.with('x', NaN)", null, "g.with(string0, number0)", + null, "g.with(\"x\", Double.NaN)", "g.with_(\"x\", Number.NaN)", "g.with_('x', float('nan'))"}, {"g.with('x', Infinity)", null, "g.with(string0, number0)", + null, "g.with(\"x\", Double.POSITIVE_INFINITY)", "g.with_(\"x\", Number.POSITIVE_INFINITY)", "g.with_('x', float('inf'))"}, {"g.with('x', -Infinity)", null, "g.with(string0, number0)", + null, "g.with(\"x\", Double.NEGATIVE_INFINITY)", "g.with_(\"x\", Number.NEGATIVE_INFINITY)", "g.with_('x', float('-inf'))"}, {"g.with('x', 1.0)", null, "g.with(string0, number0)", + null, "g.with(\"x\", 1.0)", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)",}, {"g.with('x', 1.0D)", - null, + "g.with('x', 1.0d)", "g.with(string0, double0)", + "g.with('x', 1.0d)", "g.with(\"x\", 1.0d)", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)"}, {"g.with('x', 1.0d)", null, "g.with(string0, double0)", + null, "g.with(\"x\", 1.0d)", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)"}, {"g.with('x', -1.0d)", null, "g.with(string0, double0)", + null, "g.with(\"x\", -1.0d)", "g.with_(\"x\", -1.0)", "g.with_('x', -1.0)"}, {"g.with('x', 1.0F)", - null, + "g.with('x', 1.0f)", "g.with(string0, float0)", + "g.with('x', 1.0f)", "g.with(\"x\", 1.0f)", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)"}, {"g.with('x', 1.0f)", null, "g.with(string0, float0)", + null, "g.with(\"x\", 1.0f)", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)"}, {"g.with('x', -1.0F)", - null, + "g.with('x', -1.0f)", "g.with(string0, float0)", + "g.with('x', -1.0f)", "g.with(\"x\", -1.0f)", "g.with_(\"x\", -1.0)", "g.with_('x', -1.0)"}, {"g.with('x', 1.0m)", null, "g.with(string0, bigdecimal0)", + "g.with('x', 1.0g)", "g.with(\"x\", new BigDecimal(\"1.0\"))", "g.with_(\"x\", 1.0)", "g.with_('x', 1.0)"}, {"g.with('x', -1.0m)", null, "g.with(string0, bigdecimal0)", + "g.with('x', -1.0g)", "g.with(\"x\", new BigDecimal(\"-1.0\"))", "g.with_(\"x\", -1.0)", "g.with_('x', -1.0)"}, {"g.with('x', -1.0M)", - null, + "g.with('x', -1.0m)", "g.with(string0, bigdecimal0)", + "g.with('x', -1.0g)", "g.with(\"x\", new BigDecimal(\"-1.0\"))", "g.with_(\"x\", -1.0)", "g.with_('x', -1.0)"}, {"g.with('x', 1b)", null, "g.with(string0, byte0)", + "g.with('x', new Byte(1))", "g.with(\"x\", new Byte(1))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', 1B)", - null, + "g.with('x', 1b)", "g.with(string0, byte0)", + "g.with('x', new Byte(1))", "g.with(\"x\", new Byte(1))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', -1b)", null, "g.with(string0, byte0)", + "g.with('x', new Byte(-1))", "g.with(\"x\", new Byte(-1))", "g.with_(\"x\", -1)", "g.with_('x', -1)"}, {"g.with('x', 1s)", null, "g.with(string0, short0)", + "g.with('x', new Short(1))", "g.with(\"x\", new Short(1))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', -1s)", null, "g.with(string0, short0)", + "g.with('x', new Short(-1))", "g.with(\"x\", new Short(-1))", "g.with_(\"x\", -1)", "g.with_('x', -1)"}, {"g.with('x', 1S)", - null, + "g.with('x', 1s)", "g.with(string0, short0)", + "g.with('x', new Short(1))", "g.with(\"x\", new Short(1))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', 1i)", null, "g.with(string0, integer0)", + null, "g.with(\"x\", 1)", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', 1I)", - null, + "g.with('x', 1i)", "g.with(string0, integer0)", + "g.with('x', 1i)", "g.with(\"x\", 1)", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', -1i)", null, "g.with(string0, integer0)", + null, "g.with(\"x\", -1)", "g.with_(\"x\", -1)", "g.with_('x', -1)"}, {"g.with('x', 1l)", null, "g.with(string0, long0)", + null, "g.with(\"x\", 1l)", "g.with_(\"x\", 1)", "g.with_('x', long(1))"}, {"g.with('x', 1L)", - null, + "g.with('x', 1l)", "g.with(string0, long0)", + "g.with('x', 1l)", "g.with(\"x\", 1l)", "g.with_(\"x\", 1)", "g.with_('x', long(1))"}, {"g.with('x', -1l)", null, "g.with(string0, long0)", + null, "g.with(\"x\", -1l)", "g.with_(\"x\", -1)", "g.with_('x', long(-1))"}, {"g.with('x', 1n)", null, "g.with(string0, biginteger0)", + "g.with('x', 1g)", "g.with(\"x\", new BigInteger(\"1\"))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', 1N)", - null, + "g.with('x', 1n)", "g.with(string0, biginteger0)", + "g.with('x', 1g)", "g.with(\"x\", new BigInteger(\"1\"))", "g.with_(\"x\", 1)", "g.with_('x', 1)"}, {"g.with('x', -1n)", null, "g.with(string0, biginteger0)", + "g.with('x', -1g)", "g.with(\"x\", new BigInteger(\"-1\"))", "g.with_(\"x\", -1)", "g.with_('x', -1)"}, {"g.with('x', datetime('2023-08-02T00:00:00Z'))", null, "g.with(string0, date0)", + null, "g.with(\"x\", new Date(1690934400000))", "g.with_(\"x\", new Date(1690934400000))", "g.with_('x', datetime.datetime.utcfromtimestamp(1690934400000 / 1000.0))"}, {"g.with('x', [x: 1])", "g.with('x', [x:1])", "g.with(string0, map0)", + "g.with('x', [x:1])", "g.with(\"x\", new LinkedHashMap<Object, Object>() {{ put(\"x\", 1); }})", "g.with_(\"x\", new Map([[\"x\", 1]]))", "g.with_('x', { 'x': 1 })"}, {"g.with('x', [x:1, new:2])", null, "g.with(string0, map0)", + null, "g.with(\"x\", new LinkedHashMap<Object, Object>() {{ put(\"x\", 1); put(\"new\", 2); }})", "g.with_(\"x\", new Map([[\"x\", 1], [\"new\", 2]]))", "g.with_('x', { 'x': 1, 'new': 2 })"}, {"g.with('x', [\"x\":1])", null, "g.with(string0, map0)", + null, "g.with(\"x\", new LinkedHashMap<Object, Object>() {{ put(\"x\", 1); }})", "g.with_(\"x\", new Map([[\"x\", 1]]))", "g.with_('x', { 'x': 1 })"}, {"g.with('x', [1:'x'])", null, "g.with(string0, map0)", + null, "g.with(\"x\", new LinkedHashMap<Object, Object>() {{ put(1, \"x\"); }})", "g.with_(\"x\", new Map([[1, \"x\"]]))", "g.with_('x', { 1: 'x' })"}, {"g.with('x', [1, 'x'])", null, "g.with(string0, list0)", + null, "g.with(\"x\", new ArrayList<Object>() {{ add(1); add(\"x\"); }})", "g.with_(\"x\", [1, \"x\"])", "g.with_('x', [1, 'x'])"}, {"g.with('x', 0..5)", null, "g.with(string0, number0..number1)", + "g.with('x', 0..5)", "Java does not support range literals", "Javascript does not support range literals", "Python does not support range literals"}, @@ -362,26 +414,31 @@ public class GremlinTranslatorTest { "g.withBulk(boolean0)", null, null, + null, "g.with_bulk(False)"}, {"g.withBulk(true)", null, "g.withBulk(boolean0)", null, null, + null, "g.with_bulk(True)"}, {"g.withBulk( true )", "g.withBulk(true)", "g.withBulk(boolean0)", "g.withBulk(true)", "g.withBulk(true)", + "g.withBulk(true)", "g.with_bulk(True)"}, {"g.withBulk(x)", null, null, null, null, + null, "g.with_bulk(x)"}, {"g.withStrategies(ReadOnlyStrategy)", + null, null, null, "g.withStrategies(ReadOnlyStrategy.instance())", @@ -390,18 +447,21 @@ public class GremlinTranslatorTest { {"g.withStrategies(new SeedStrategy(seed:10000))", null, "g.withStrategies(new SeedStrategy(seed:number0))", + null, "g.withStrategies(SeedStrategy.build().seed(10000).create())", "g.withStrategies(new SeedStrategy({seed: 10000}))", "g.with_strategies(SeedStrategy(seed=10000))"}, {"g.withStrategies(new PartitionStrategy(includeMetaProperties: true, partitionKey:'x'))", "g.withStrategies(new PartitionStrategy(includeMetaProperties:true, partitionKey:'x'))", "g.withStrategies(new PartitionStrategy(includeMetaProperties:boolean0, partitionKey:string0))", + "g.withStrategies(new PartitionStrategy(includeMetaProperties:true, partitionKey:'x'))", "g.withStrategies(PartitionStrategy.build().includeMetaProperties(true).partitionKey(\"x\").create())", "g.withStrategies(new PartitionStrategy({includeMetaProperties: true, partitionKey: \"x\"}))", "g.with_strategies(PartitionStrategy(include_meta_properties=True, partition_key='x'))"}, {"g.withStrategies(new SubgraphStrategy(vertices:__.has('name', 'vadas'), edges: has('weight', gt(0.5))))", "g.withStrategies(new SubgraphStrategy(vertices:__.has('name', 'vadas'), edges:__.has('weight', P.gt(0.5))))", "g.withStrategies(new SubgraphStrategy(vertices:__.has(string0, string1), edges:__.has(string2, P.gt(number0))))", + "g.withStrategies(new SubgraphStrategy(vertices:__.has('name', 'vadas'), edges:__.has('weight', P.gt(0.5))))", "g.withStrategies(SubgraphStrategy.build().vertices(__.has(\"name\", \"vadas\")).edges(__.has(\"weight\", P.gt(0.5))).create())", "g.withStrategies(new SubgraphStrategy({vertices: __.has(\"name\", \"vadas\"), edges: __.has(\"weight\", P.gt(0.5))}))", "g.with_strategies(SubgraphStrategy(vertices=__.has('name', 'vadas'), edges=__.has('weight', P.gt(0.5))))"}, @@ -411,12 +471,14 @@ public class GremlinTranslatorTest { " __.has(\"weight\", 1.0).hasLabel(\"created\")))).E()", "g.withStrategies(new SubgraphStrategy(checkAdjacentVertices:false, vertices:__.has(\"name\", P.within(\"josh\", \"lop\", \"ripple\")), edges:__.or(__.has(\"weight\", 0.4).hasLabel(\"created\"), __.has(\"weight\", 1.0).hasLabel(\"created\")))).E()", "g.withStrategies(new SubgraphStrategy(checkAdjacentVertices:boolean0, vertices:__.has(string0, P.within(string1, string2, string3)), edges:__.or(__.has(string4, number0).hasLabel(string5), __.has(string4, number1).hasLabel(string5)))).E()", + "g.withStrategies(new SubgraphStrategy(checkAdjacentVertices:false, vertices:__.has(\"name\", P.within(\"josh\", \"lop\", \"ripple\")), edges:__.or(__.has(\"weight\", 0.4).hasLabel(\"created\"), __.has(\"weight\", 1.0).hasLabel(\"created\")))).E()", "g.withStrategies(SubgraphStrategy.build().checkAdjacentVertices(false).vertices(__.has(\"name\", P.within(\"josh\", \"lop\", \"ripple\"))).edges(__.or(__.has(\"weight\", 0.4).hasLabel(\"created\"), __.has(\"weight\", 1.0).hasLabel(\"created\"))).create()).E()", "g.withStrategies(new SubgraphStrategy({checkAdjacentVertices: false, vertices: __.has(\"name\", P.within(\"josh\", \"lop\", \"ripple\")), edges: __.or(__.has(\"weight\", 0.4).hasLabel(\"created\"), __.has(\"weight\", 1.0).hasLabel(\"created\"))})).E()", "g.with_strategies(SubgraphStrategy(check_adjacent_vertices=False, vertices=__.has('name', P.within('josh', 'lop', 'ripple')), edges=__.or_(__.has('weight', 0.4).has_label('created'), __.has('weight', 1.0).has_label('created')))).E()"}, {"g.inject(0..5)", null, "g.inject(number0..number1)", + "g.inject(0..5)", "Java does not support range literals", "Javascript does not support range literals", "Python does not support range literals"}, @@ -425,28 +487,33 @@ public class GremlinTranslatorTest { "g.inject(number0).asDate()", null, null, + null, "g.inject(long(1694017707000)).as_date()"}, {"g.V().hasLabel(null)", null, "g.V().hasLabel(string0)", null, null, + null, "g.V().has_label(None)",}, {"g.V().hasLabel('person')", null, "g.V().hasLabel(string0)", + "g.V().hasLabel('person')", "g.V().hasLabel(\"person\")", "g.V().hasLabel(\"person\")", "g.V().has_label('person')"}, {"g.V().hasLabel('person', 'software', 'class')", null, "g.V().hasLabel(string0, string1, string2)", + "g.V().hasLabel('person', 'software', 'class')", "g.V().hasLabel(\"person\", \"software\", \"class\")", "g.V().hasLabel(\"person\", \"software\", \"class\")", "g.V().has_label('person', 'software', 'class')"}, {"g.V().hasLabel(null, 'software', 'class')", null, "g.V().hasLabel(string0, string1, string2)", + "g.V().hasLabel(null, 'software', 'class')", "g.V().hasLabel(null, \"software\", \"class\")", "g.V().hasLabel(null, \"software\", \"class\")", "g.V().has_label(None, 'software', 'class')"}, @@ -455,106 +522,124 @@ public class GremlinTranslatorTest { null, null, null, + null, null}, {"g.V().map(out().count())", "g.V().map(__.out().count())", "g.V().map(__.out().count())", "g.V().map(__.out().count())", "g.V().map(__.out().count())", + "g.V().map(__.out().count())", "g.V().map(__.out().count())"}, {"g.V().fold().count(Scope.local)", null, null, null, null, + null, null}, {"g.V().fold().count(Scope.local)", null, null, null, null, + null, null}, {"g.V().has(T.id, 1)", null, "g.V().has(T.id, number0)", null, null, + null, "g.V().has(T.id_, 1)"}, {"g.V().has(id, 1)", "g.V().has(T.id, 1)", "g.V().has(T.id, number0)", "g.V().has(T.id, 1)", "g.V().has(T.id, 1)", + "g.V().has(T.id, 1)", "g.V().has(T.id_, 1)"}, {"g.V().has(\"name\", P.within(\"josh\",\"stephen\"))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has(string0, P.within(string1, string2))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", + "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has('name', P.within('josh', 'stephen'))"}, {"g.V().has(\"name\", P.eq(\"josh\"))", null, "g.V().has(string0, P.eq(string1))", null, null, + null, "g.V().has('name', P.eq('josh'))"}, {"g.V().has(\"name\", P.eq(\"josh\").negate())", null, "g.V().has(string0, P.eq(string1).negate())", null, null, + null, "g.V().has('name', P.eq('josh').negate())"}, {"g.V().has(\"name\", P.within())", null, "g.V().has(string0, P.within())", null, null, + null, "g.V().has('name', P.within())"}, {"g.V().has(\"name\", P.within(\"josh\",\"stephen\").or(eq(\"vadas\")))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\").or(P.eq(\"vadas\")))", "g.V().has(string0, P.within(string1, string2).or(P.eq(string3)))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\").or(P.eq(\"vadas\")))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\").or(P.eq(\"vadas\")))", + "g.V().has(\"name\", P.within(\"josh\", \"stephen\").or(P.eq(\"vadas\")))", "g.V().has('name', P.within('josh', 'stephen').or_(P.eq('vadas')))"}, {"g.V().has(\"name\", within(\"josh\", \"stephen\"))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has(string0, P.within(string1, string2))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", + "g.V().has(\"name\", P.within(\"josh\", \"stephen\"))", "g.V().has('name', P.within('josh', 'stephen'))"}, {"g.V().has(\"name\", TextP.containing(\"j\").negate())", null, "g.V().has(string0, TextP.containing(string1).negate())", null, null, + null, "g.V().has('name', TextP.containing('j').negate())"}, {"g.V().hasLabel(\"person\").has(\"age\", P.not(P.lte(10).and(P.not(P.between(11, 20)))).and(P.lt(29).or(P.eq(35)))).values(\"name\")", null, "g.V().hasLabel(string0).has(string1, P.not(P.lte(number0).and(P.not(P.between(number1, number2)))).and(P.lt(number3).or(P.eq(number4)))).values(string2)", null, null, + null, "g.V().has_label('person').has('age', P.not_(P.lte(10).and_(P.not_(P.between(11, 20)))).and_(P.lt(29).or_(P.eq(35)))).values('name')"}, {"g.V().has(\"name\", containing(\"j\"))", "g.V().has(\"name\", TextP.containing(\"j\"))", "g.V().has(string0, TextP.containing(string1))", "g.V().has(\"name\", TextP.containing(\"j\"))", "g.V().has(\"name\", TextP.containing(\"j\"))", + "g.V().has(\"name\", TextP.containing(\"j\"))", "g.V().has('name', TextP.containing('j'))"}, {"g.V().property(set, \"name\", \"stephen\")", "g.V().property(Cardinality.set, \"name\", \"stephen\")", "g.V().property(Cardinality.set, string0, string1)", "g.V().property(Cardinality.set, \"name\", \"stephen\")", "g.V().property(Cardinality.set, \"name\", \"stephen\")", + "g.V().property(Cardinality.set, \"name\", \"stephen\")", "g.V().property(Cardinality.set_, 'name', 'stephen')"}, {"g.V().property(Cardinality.set, \"name\", \"stephen\")", null, "g.V().property(Cardinality.set, string0, string1)", null, null, + null, "g.V().property(Cardinality.set_, 'name', 'stephen')"}, {"g.V().has('name', 'foo').property([\"name\":Cardinality.set(\"bar\"), \"age\":43])", null, "g.V().has(string0, string1).property(map0)", + null, "g.V().has(\"name\", \"foo\").property(new LinkedHashMap<Object, Object>() {{ put(\"name\", Cardinality.set(\"bar\")); put(\"age\", 43); }})", "g.V().has(\"name\", \"foo\").property(new Map([[\"name\", CardinalityValue.set(\"bar\")], [\"age\", 43]]))", "g.V().has('name', 'foo').property({ 'name': CardinalityValue.set_('bar'), 'age': 43 })"}, @@ -562,6 +647,7 @@ public class GremlinTranslatorTest { null, "g.V(new Vertex(number0, string0)).limit(number0)", "g.V(new DetachedVertex(1, \"person\")).limit(1)", + "g.V(new DetachedVertex(1, \"person\")).limit(1)", "g.V(new Vertex(1, \"person\")).limit(1)", "g.V(Vertex(1, 'person')).limit(1)",}, {"g.V().both().properties().dedup().hasKey(\"age\").value()", @@ -569,11 +655,13 @@ public class GremlinTranslatorTest { "g.V().both().properties().dedup().hasKey(string0).value()", null, null, + null, "g.V().both().properties().dedup().has_key('age').value()",}, {"g.V().connectedComponent().with(ConnectedComponent.propertyName, \"component\")", "g.V().connectedComponent().with(ConnectedComponent.propertyName, \"component\")", "g.V().connectedComponent().with(ConnectedComponent.propertyName, string0)", "g.V().connectedComponent().with(ConnectedComponent.propertyName, \"component\")", + "g.V().connectedComponent().with(ConnectedComponent.propertyName, \"component\")", "g.V().connectedComponent().with_(ConnectedComponent.propertyName, \"component\")", "g.V().connected_component().with_(ConnectedComponent.property_name, 'component')"}, {"g.withSideEffect(\"c\", xx2).withSideEffect(\"m\", xx3).mergeE(xx1).option(Merge.onCreate, __.select(\"c\")).option(Merge.onMatch, __.select(\"m\"))", @@ -581,23 +669,27 @@ public class GremlinTranslatorTest { "g.withSideEffect(string0, xx2).withSideEffect(string1, xx3).mergeE(map0).option(Merge.onCreate, __.select(string0)).option(Merge.onMatch, __.select(string1))", null, null, + null, "g.with_side_effect('c', xx2).with_side_effect('m', xx3).merge_e(xx1).option(Merge.on_create, __.select('c')).option(Merge.on_match, __.select('m'))"}, {"g.V(1, 2, 3)", null, "g.V(number0, number1, number2)", null, null, + null, null}, {"g.V().limit(1)", null, "g.V().limit(number0)", null, null, + null, null}, {"g.V().limit(1L)", - null, + "g.V().limit(1l)", "g.V().limit(long0)", "g.V().limit(1l)", + "g.V().limit(1l)", "g.V().limit(1)", "g.V().limit(long(1))"}, {"g.V().limit(x)", @@ -605,36 +697,42 @@ public class GremlinTranslatorTest { null, null, null, + null, null}, {"g.V().toList()", null, null, null, null, + null, "g.V().to_list()"}, {"g.V().iterate()", null, null, null, null, + null, null}, {"g.tx().commit()", null, null, null, null, + null, null}, }); } public TranslationTest(final String query, final String expectedForLang, final String expectedForAnonymized, + final String expectedForGroovy, final String expectedForJava, final String expectedForJavascript, final String expectedForPython) { this.query = query; this.expectedForLang = expectedForLang != null ? expectedForLang : query; this.expectedForAnonymized = expectedForAnonymized != null ? expectedForAnonymized : query; + this.expectedForGroovy = expectedForGroovy != null ? expectedForGroovy : query; this.expectedForJava = expectedForJava != null ? expectedForJava : query; this.expectedForJavascript = expectedForJavascript != null ? expectedForJavascript : query; this.expectedForPython = expectedForPython != null ? expectedForPython : query; @@ -652,6 +750,16 @@ public class GremlinTranslatorTest { assertEquals(expectedForAnonymized, translatedQuery); } + @Test + public void shouldTranslateForGroovy() { + try { + final String translatedQuery = GremlinTranslator.translate(query, "g", Translator.GROOVY).getTranslated(); + assertEquals(expectedForGroovy, translatedQuery); + } catch (TranslatorException e) { + assertThat(e.getMessage(), startsWith(expectedForGroovy)); + } + } + @Test public void shouldTranslateForJava() { try { diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java index 38303b2738..c081426992 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java @@ -32,6 +32,8 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.apache.tinkerpop.gremlin.language.grammar.GremlinAntlrToJava; import org.apache.tinkerpop.gremlin.language.grammar.GremlinLexer; import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; +import org.apache.tinkerpop.gremlin.language.translator.GremlinTranslator; +import org.apache.tinkerpop.gremlin.language.translator.Translator; import org.apache.tinkerpop.gremlin.process.traversal.Merge; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; @@ -443,7 +445,11 @@ public final class StepDefinition { } private Traversal parseGremlin(final String script) { - final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script)); + // tests the normalizer by running the script from the feature file first + final String normalizedGremlin = GremlinTranslator.translate(script, Translator.LANGUAGE).getTranslated(); + + // parse the Gremlin to a Traversal + final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(normalizedGremlin)); final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer)); final GremlinParser.QueryContext ctx = parser.query(); return (Traversal) new GremlinAntlrToJava(g).visitQuery(ctx);
