This is an automated email from the ASF dual-hosted git repository. kenhuuu pushed a commit to branch stringify-params in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit bb0d29eaeb1a62ede89a63b5a06883a4be573e68 Author: Ken Hu <[email protected]> AuthorDate: Mon Apr 20 20:37:06 2026 -0700 translations + feature tests --- .../translator/AnonymizedTranslatorVisitor.java | 17 + .../translator/DotNetTranslateVisitor.java | 28 ++ .../language/translator/GoTranslateVisitor.java | 28 ++ .../translator/GroovyTranslateVisitor.java | 25 ++ .../language/translator/JavaTranslateVisitor.java | 26 ++ .../translator/JavascriptTranslateVisitor.java | 18 + .../translator/PythonTranslateVisitor.java | 33 ++ .../language/translator/TranslateVisitor.java | 18 + .../language/translator/GremlinTranslatorTest.java | 27 ++ .../gremlin/process/traversal/GremlinLangTest.java | 17 +- .../Gherkin/CommonSteps.cs | 18 + .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 42 ++- .../Process/Traversal/GremlinLangTests.cs | 79 +---- gremlin-go/build/generate.groovy | 10 +- gremlin-go/driver/cucumber/cucumberSteps_test.go | 52 ++- gremlin-go/driver/cucumber/gremlin.go | 22 ++ gremlin-go/driver/gremlinlang_test.go | 50 +-- .../translator/AnonymizedTranslateVisitor.ts | 12 + .../language/translator/DotNetTranslateVisitor.ts | 21 ++ .../lib/language/translator/GoTranslateVisitor.ts | 67 ++++ .../language/translator/GroovyTranslateVisitor.ts | 21 ++ .../language/translator/JavaTranslateVisitor.ts | 21 ++ .../translator/JavascriptTranslateVisitor.ts | 14 + .../language/translator/PythonTranslateVisitor.ts | 69 ++++ .../lib/language/translator/TranslateVisitor.ts | 12 + .../scripts/groovy/generate.groovy | 10 +- .../test/cucumber/feature-steps.js | 6 + .../gremlin-javascript/test/cucumber/gremlin.js | 22 ++ .../gremlin-javascript/test/cucumber/world.js | 8 + .../test/unit/gremlin-lang-test.js | 13 +- .../unit/translator/gremlin-translator-test.js | 57 +++ .../src/main/python/tests/feature/feature_steps.py | 23 +- .../src/main/python/tests/feature/gremlin.py | 22 ++ .../python/tests/unit/process/test_gremlin_lang.py | 55 +-- .../tinkerpop/gremlin/features/StepDefinition.java | 11 + .../gremlin/language/translator/translations.json | 387 +++++++++++++++++++++ .../gremlin/test/features/data/Binary.feature | 82 +++++ .../gremlin/test/features/data/Char.feature | 94 +++++ .../gremlin/test/features/data/Duration.feature | 154 ++++++++ 39 files changed, 1472 insertions(+), 219 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/AnonymizedTranslatorVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/AnonymizedTranslatorVisitor.java index 98c8be8ef7..23b70526bc 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/AnonymizedTranslatorVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/AnonymizedTranslatorVisitor.java @@ -23,6 +23,8 @@ import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; import java.math.BigDecimal; import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.time.Duration; import java.time.OffsetDateTime; import java.util.HashMap; import java.util.List; @@ -187,4 +189,19 @@ public class AnonymizedTranslatorVisitor extends TranslateVisitor { @Override public Void visitUuidLiteral(final GremlinParser.UuidLiteralContext ctx) { return anonymize(ctx, String.class); } + + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + return anonymize(ctx, Character.class); + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + return anonymize(ctx, Duration.class); + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + return anonymize(ctx, ByteBuffer.class); + } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/DotNetTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/DotNetTranslateVisitor.java index 41fcaa4feb..f80123c4da 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/DotNetTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/DotNetTranslateVisitor.java @@ -1181,6 +1181,34 @@ public class DotNetTranslateVisitor extends AbstractTranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + // "a"c or 'a'c -> 'a' in C# + final String text = ctx.getText(); + // strip the 'c' suffix to get the quoted character + final String withoutSuffix = text.substring(0, text.length() - 1); + // C# char literals use single quotes + final String inner = removeFirstAndLastCharacters(withoutSuffix); + sb.append("'").append(inner).append("'"); + return null; + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + sb.append("XmlConvert.ToTimeSpan("); + sb.append(ctx.stringLiteral().getText()); + sb.append(")"); + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append("Convert.FromBase64String("); + sb.append(ctx.stringLiteral().getText()); + sb.append(")"); + return null; + } + @Override public Void visitTraversalGType(GremlinParser.TraversalGTypeContext ctx) { final String[] split = ctx.getText().split("\\."); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java index 18cdfa1660..7a81b84a72 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GoTranslateVisitor.java @@ -28,8 +28,10 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.Option import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.util.DatetimeHelper; +import java.time.Duration; import java.time.OffsetDateTime; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -346,6 +348,32 @@ public class GoTranslateVisitor extends AbstractTranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + throw new TranslatorException("Character literals are not supported in Go"); + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + final String iso8601 = removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + final Duration d = Duration.parse(iso8601); + sb.append("time.Duration(").append(d.toNanos()).append(")"); + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + final String base64Str = removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + final byte[] bytes = Base64.getDecoder().decode(base64Str); + sb.append("gremlingo.ByteBuffer{Data: []byte{"); + for (int i = 0; i < bytes.length; i++) { + if (i > 0) sb.append(","); + sb.append(bytes[i] & 0xFF); + } + sb.append("}}"); + return null; + } + @Override protected String getCardinalityFunctionClass() { return "CardinalityValue"; 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 index 33139d0919..a6e1f6050a 100644 --- 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 @@ -131,6 +131,31 @@ public class GroovyTranslateVisitor extends TranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + final String text = ctx.getText(); + final String withoutSuffix = text.substring(0, text.length() - 1); + final String inner = removeFirstAndLastCharacters(withoutSuffix); + sb.append("'").append(inner).append("' as char"); + return null; + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + sb.append("Duration.parse("); + sb.append(ctx.stringLiteral().getText()); + sb.append(")"); + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append("ByteBuffer.wrap(Base64.getDecoder().decode("); + sb.append(ctx.stringLiteral().getText()); + sb.append("))"); + return null; + } + @Override public Void visitNullLiteral(final GremlinParser.NullLiteralContext ctx) { if (ctx.getParent() instanceof GremlinParser.GenericMapNullableArgumentContext) { 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 aecf7c2015..9a0c7dbdb5 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 @@ -248,6 +248,32 @@ public class JavaTranslateVisitor extends AbstractTranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + // "a"c or 'a'c -> 'a' in Java + final String text = ctx.getText(); + final String withoutSuffix = text.substring(0, text.length() - 1); + final String inner = removeFirstAndLastCharacters(withoutSuffix); + sb.append("'").append(inner).append("'"); + return null; + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + sb.append("Duration.parse("); + sb.append(ctx.stringLiteral().getText()); + sb.append(")"); + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append("ByteBuffer.wrap(Base64.getDecoder().decode("); + sb.append(ctx.stringLiteral().getText()); + sb.append("))"); + return null; + } + @Override public Void visitGenericRangeLiteral(final GremlinParser.GenericRangeLiteralContext ctx) { throw new TranslatorException("Java does not support range literals"); 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 08f097d6f7..2a8e0ed07f 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 @@ -230,6 +230,24 @@ public class JavascriptTranslateVisitor extends AbstractTranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + throw new TranslatorException("Character literals are not supported in JavaScript"); + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + throw new TranslatorException("Duration literals are not supported in JavaScript"); + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append("Buffer.from("); + visitStringLiteral(ctx.stringLiteral()); + sb.append(",'base64')"); + return null; + } + @Override protected String getCardinalityFunctionClass() { return "CardinalityValue"; 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 4473a38dbf..9a9699de88 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 @@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.util.DatetimeHelper; import java.math.BigInteger; +import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -289,6 +290,38 @@ public class PythonTranslateVisitor extends AbstractTranslateVisitor { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + final String text = ctx.getText(); + final String withoutSuffix = text.substring(0, text.length() - 1); + final String inner = removeFirstAndLastCharacters(withoutSuffix); + sb.append("SingleChar('").append(inner).append("')"); + return null; + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + final String iso8601 = removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + final Duration d = Duration.parse(iso8601); + final long totalSeconds = d.getSeconds(); + final int nanos = d.getNano(); + if (nanos == 0) { + sb.append("timedelta(seconds=").append(totalSeconds).append(")"); + } else { + final long micros = nanos / 1000; + sb.append("timedelta(seconds=").append(totalSeconds).append(",microseconds=").append(micros).append(")"); + } + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append("base64.b64decode("); + visitStringLiteral(ctx.stringLiteral()); + sb.append(")"); + return null; + } + @Override protected String getCardinalityFunctionClass() { return "CardinalityValue"; 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 5eccd5f400..a8d51f6b28 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 @@ -452,6 +452,24 @@ public class TranslateVisitor extends GremlinBaseVisitor<Void> { return null; } + @Override + public Void visitCharacterLiteral(final GremlinParser.CharacterLiteralContext ctx) { + sb.append(ctx.getText()); + return null; + } + + @Override + public Void visitDurationLiteral(final GremlinParser.DurationLiteralContext ctx) { + sb.append(ctx.getText()); + return null; + } + + @Override + public Void visitBinaryLiteral(final GremlinParser.BinaryLiteralContext ctx) { + sb.append(ctx.getText()); + return null; + } + @Override public Void visitVariable(final GremlinParser.VariableContext ctx) { final String var = ctx.getText(); 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 525a3dd1e9..cb97ae615d 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 @@ -1426,6 +1426,33 @@ public class GremlinTranslatorTest { "g.call(\"--list\").with(\"service\", __.constant(\"tinker.search\"))", "g.call(\"--list\").with_(\"service\", __.constant(\"tinker.search\"))", "g.call('--list').with_('service', __.constant('tinker.search'))"}, + {"g.inject(\"a\"c)", + null, + "g.inject(character0)", + "g.Inject<object>('a')", + "Character literals are not supported in Go", + "g.inject('a' as char)", + "g.inject('a')", + "Character literals are not supported in JavaScript", + "g.inject(SingleChar('a'))"}, + {"g.inject(Duration(\"PT2H30M\"))", + null, + "g.inject(duration0)", + "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT2H30M\"))", + "g.Inject(time.Duration(9000000000000))", + "g.inject(Duration.parse(\"PT2H30M\"))", + "g.inject(Duration.parse(\"PT2H30M\"))", + "Duration literals are not supported in JavaScript", + "g.inject(timedelta(seconds=9000))"}, + {"g.inject(Binary(\"AQID\"))", + null, + "g.inject(bytebuffer0)", + "g.Inject<object>(Convert.FromBase64String(\"AQID\"))", + "g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}})", + "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "g.inject(Buffer.from(\"AQID\",'base64'))", + "g.inject(base64.b64decode('AQID'))"}, }); } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java index 9dd7cf3c47..48d01cfa32 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinLangTest.java @@ -130,24 +130,13 @@ public class GremlinLangTest { {g.inject(GValue.of("x", "x")).V(GValue.of("ids", new int[]{1, 2, 3})), "g.inject(x).V(ids)"}, {newG().inject(GValue.of(null, "test1"), GValue.of("xx2", "test2")), "g.inject(\"test1\",xx2)"}, {newG().inject(new HashSet<>(Arrays.asList(1, 2))), "g.inject({1,2})"}, - // Character literals - {g.inject('a'), "g.inject(\"a\"c)"}, - {g.inject('"'), "g.inject(\"\\\"\"c)"}, + // Character - single quote (no feature equivalent since grammar uses double quotes in feature files) {g.inject('\''), "g.inject(\"'\"c)"}, - {g.inject('\\'), "g.inject(\"\\\\\"c)"}, - {g.inject('\u00e9'), "g.inject(\"\\u00E9\"c)"}, - // Duration literals - {g.inject(Duration.ofHours(2).plusMinutes(30)), "g.inject(Duration(\"PT2H30M\"))"}, - {g.inject(Duration.ZERO), "g.inject(Duration(\"PT0S\"))"}, - {g.inject(Duration.ofSeconds(-30)), "g.inject(Duration(\"PT-30S\"))"}, - {g.inject(Duration.ofMillis(500)), "g.inject(Duration(\"PT0.5S\"))"}, + // Duration - Java-specific: normalization of days to hours, nanosecond precision {g.inject(Duration.ofDays(1).plusHours(12)), "g.inject(Duration(\"PT36H\"))"}, {g.inject(Duration.ofNanos(1)), "g.inject(Duration(\"PT0.000000001S\"))"}, {g.inject(Duration.ofMillis(-500)), "g.inject(Duration(\"PT-0.5S\"))"}, - // Binary literals - {g.inject(ByteBuffer.wrap(new byte[]{1, 2, 3})), "g.inject(Binary(\"AQID\"))"}, - {g.inject(ByteBuffer.wrap(new byte[]{})), "g.inject(Binary(\"\"))"}, - {g.inject(ByteBuffer.wrap(new byte[]{0})), "g.inject(Binary(\"AA==\"))"}, + // Binary - byte[] type (distinct from ByteBuffer) {g.inject(new byte[]{1, 2, 3}), "g.inject(Binary(\"AQID\"))"}, {g.inject(new byte[]{}), "g.inject(Binary(\"\"))"}, {g.inject(new byte[]{0}), "g.inject(Binary(\"AA==\"))"}, diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs index bb3cf3e671..203317c18d 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs @@ -63,6 +63,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {@"prop\[(.+)\]", ToProperty}, {@"dt\[(.+)\]", ToDateTime}, {@"uuid\[(.+)\]", ToUuid}, + {@"char\[(.)\]", ToChar}, + {@"dur\[(.+)\]", ToDuration}, + {@"bin\[(.*)\]", ToBinary}, {@"d\[(.*)\]\.([bsilfdmn])", ToNumber}, {@"D\[(.+)\]", ToDirection}, {@"M\[(.+)\]", ToMerge}, @@ -541,6 +544,21 @@ namespace Gremlin.Net.IntegrationTest.Gherkin return null; } + private static object ToChar(string value, string graphName) + { + return value[0]; + } + + private static object ToDuration(string iso8601, string graphName) + { + return System.Xml.XmlConvert.ToTimeSpan(iso8601); + } + + private static object ToBinary(string base64, string graphName) + { + return Convert.FromBase64String(base64); + } + private static Vertex ToVertex(string name, string graphName) { if (ScenarioData.GetByGraphName(graphName).Vertices.ContainsKey(name)) diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index e0c6c08853..d2ffc824b7 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -226,6 +226,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_V_valuesXintX_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_project_byXidentityX_byXmathXaddX999XXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("int", 50), (g,p) =>g.V().Values<object>("int").AsNumber(GType.BigInt).Is(P.TypeOf(GType.BigInt)).Project<object>("original", "added").By(__.Identity()).By(__.Math("_ + 999"))}}, {"g_injectX777X_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_groupCount", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(777).AsNumber(GType.BigInt).Is(P.TypeOf(GType.BigInt)).GroupCount<object>()}}, {"g_V_valuesXageX_isXtypeOfXGType_BIGINTXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Is(P.TypeOf(GType.BigInt))}}, + {"g_injectXBinaryXAQIDXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Convert.FromBase64String("AQID"))}}, + {"g_injectXBinaryXemptyXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Convert.FromBase64String(""))}}, + {"g_injectXBinaryXAA_eqeqXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Convert.FromBase64String("AA=="))}}, + {"g_valuesXblobX_isXtypeOfXGType_BINARYXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("blob", Convert.FromBase64String("AQID")), (g,p) =>g.V().Values<object>("blob").Is(P.TypeOf(GType.Binary))}}, + {"g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Convert.FromBase64String("AQID")).Is(P.Eq(Convert.FromBase64String("AQID")))}}, {"g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("int", 5), (g,p) =>g.V().Values<object>("int").AsNumber(GType.Byte).Is(P.TypeOf(GType.Byte))}}, {"g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_mathXaddX20XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("int", 10), (g,p) =>g.V().Values<object>("int").AsNumber(GType.Byte).Is(P.TypeOf(GType.Byte)).Math("_ + 20")}}, {"g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_isXltX10XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("int", 7), (g,p) =>g.V().Values<object>("int").AsNumber(GType.Byte).Is(P.TypeOf(GType.Byte)).Is(P.Lt(10))}}, @@ -234,6 +239,12 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_chooseXisXeqX12XX_constantXtwelveX_constantXotherXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("int", 12), (g,p) =>g.V().Values<object>("int").AsNumber(GType.Byte).Is(P.TypeOf(GType.Byte)).Choose<object>(__.Is(P.Eq(12)), __.Constant<object>("twelve"), __.Constant<object>("other"))}}, {"g_injectX15X_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_groupCount", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(15).AsNumber(GType.Byte).Is(P.TypeOf(GType.Byte)).GroupCount<object>()}}, {"g_V_valuesXageX_isXtypeOfXGType_BYTEXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Is(P.TypeOf(GType.Byte))}}, + {"g_injectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>('a')}}, + {"g_injectXescaped_quoteX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>('\"')}}, + {"g_injectXescaped_backslashX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>('\\')}}, + {"g_injectXunicodeX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>('\u00E9')}}, + {"g_valuesXinitialX_isXtypeOfXGType_CHARXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("initial", 'a'), (g,p) =>g.V().Values<object>("initial").Is(P.TypeOf(GType.Char))}}, + {"g_injectXaX_isXeqXaXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>('a').Is(P.Eq('a'))}}, {"g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "event").Property("datetime", DateTimeOffset.Parse("2023-08-08T00:00Z")), (g,p) =>g.V().Values<object>("datetime").Is(P.TypeOf(GType.DateTime))}}, {"g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_project_byXidentityX_byXdateAddXDT_dayX1XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "event").Property("datetime", DateTimeOffset.Parse("2023-08-08T00:00Z")), (g,p) =>g.V().Values<object>("datetime").Is(P.TypeOf(GType.DateTime)).Project<object>("original", "nextDay").By(__.Identity()).By(__.DateAdd(DT.Day, 1))}}, {"g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_dateDiffXdatetimeX2023_08_10XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "event").Property("datetime", DateTimeOffset.Parse("2023-08-08T00:00Z")), (g,p) =>g.V().Values<object>("datetime").Is(P.TypeOf(GType.DateTime)).DateDiff(DateTimeOffset.Parse("2023-08-08T00:00:30Z"))}}, @@ -253,6 +264,17 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_V_valuesXdoubleX_isXtypeOfXGType_DOUBLEXX_order_byXascX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("double", 3.2d).AddV((string) "data").Property("double", 1.8d).AddV((string) "data").Property("double", 2.5d), (g,p) =>g.V().Values<object>("double").Is(P.TypeOf(GType.Double)).Order().By(Order.Asc)}}, {"g_injectX5_5dX_isXtypeOfXGType_DOUBLEXX_groupCount", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(5.5d).Is(P.TypeOf(GType.Double)).GroupCount<object>()}}, {"g_V_valuesXageX_isXtypeOfXGType_DOUBLEXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Is(P.TypeOf(GType.Double))}}, + {"g_injectXDurationXPT2H30MXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT2H30M"))}}, + {"g_injectXDurationXPT0SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT0S"))}}, + {"g_injectXDurationXPTneg30SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT-30S"))}}, + {"g_injectXDurationXnegPT30SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("-PT30S"))}}, + {"g_injectXDurationXPT0_5SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT0.5S"))}}, + {"g_injectXDurationXP1DT12HXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("P1DT12H"))}}, + {"g_injectXDurationXP2DXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("P2D"))}}, + {"g_injectXDurationXPT1H30M15SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT1H30M15S"))}}, + {"g_injectXDurationXPTneg0_5SXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT-0.5S"))}}, + {"g_valuesXlengthX_isXtypeOfXGType_DURATIONXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("length", XmlConvert.ToTimeSpan("PT2H30M")), (g,p) =>g.V().Values<object>("length").Is(P.TypeOf(GType.Duration))}}, + {"g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(XmlConvert.ToTimeSpan("PT2H30M")).Is(P.Gt(XmlConvert.ToTimeSpan("PT1H")))}}, {"g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("float", 2.5), (g,p) =>g.V().Values<object>("float").AsNumber(GType.Float).Is(P.TypeOf(GType.Float))}}, {"g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_mathXmulX2XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("float", 3.0), (g,p) =>g.V().Values<object>("float").AsNumber(GType.Float).Is(P.TypeOf(GType.Float)).Math("_ * 2")}}, {"g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_isXeqX1_5XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "data").Property("float", 1.5), (g,p) =>g.V().Values<object>("float").AsNumber(GType.Float).Is(P.TypeOf(GType.Float)).Is(P.Eq(1.5))}}, @@ -1060,7 +1082,6 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_hasLabelXpersonX_valuesXnameX_asXaX_constantXMrX_concatXselectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").As("a").Constant<object>("Mr.").Concat(__.Select<object>("a"))}}, {"g_hasLabelXsoftwareX_asXaX_valuesXnameX_concatXunsesX_concatXselectXaXvaluesXlangX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").As("a").Values<object>("name").Concat(" uses ").Concat(__.Select<object>("a").Values<object>("lang"))}}, {"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_labelX_concatXselectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label()).Concat(__.Select<object>("a").InV().Values<object>("name"))}}, -<<<<<<< HEAD {"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_label_selectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label(), __.Select<object>("a").InV().Values<object>("name"))}}, {"g_addVXconstantXprefix_X_concatXVX1X_labelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV( [...] {"g_injectXnullX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Conjoin((string) "1")}}, @@ -1074,23 +1095,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_V_out_out_path_byXnameX_conjoinXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Path().By("name").Conjoin((string) "")}}, {"g_injectXa_null_bX_conjoinXxyzX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { "a", null, "b" }).Conjoin((string) "xyz")}}, {"g_injectX3_threeX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { 3, "three" }).Conjoin((string) ";")}}, -======= - {"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_label_selectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label(),__.Select<object>("a").InV().Values<object>("name"))}}, - {"g_addVXconstantXprefix_X_concatXVX1X_labelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("age",32).As("josh").AddV("software").Property("name","ripple").Proper [...] - {"g_injectXnullX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Conjoin("1")}}, - {"g_V_valuesXnameX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Conjoin("1")}}, - {"g_V_valuesXnonexistantX_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("nonexistant").Fold().Conjoin(";")}}, - {"g_V_valuesXnameX_order_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order().Fold().Conjoin("_")}}, - {"g_V_valuesXageX_order_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Order().Fold().Conjoin(";")}}, - {"g_V_out_path_byXvaluesXnameX_toUpperX_conjoinXMARKOX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Path().By(__.Values<object>("name").ToUpper()).Conjoin("MARKO")}}, - {"g_injectXmarkoX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("-")}}, - {"g_V_valueMapXlocationX_selectXvaluesX_unfold_orderXlocalX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().ValueMap<object,object>("location").Select<object>(Column.Values).Unfold<object>().Order(Scope.Local).Conjoin("1")}}, - {"g_V_out_out_path_byXnameX_conjoinXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Path().By("name").Conjoin("")}}, - {"g_injectXa_null_bX_conjoinXxyzX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("xyz")}}, - {"g_injectX3_threeX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin(";")}}, - {"g_injectXnull_a_null_bX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}}, - {"g_injectXnull_nullX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}}, ->>>>>>> 3.7-dev + {"g_injectXnull_a_null_bX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { null, "a", null, "b" }).Conjoin((string) "+")}}, + {"g_injectXnull_nullX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { null, null }).Conjoin((string) "+")}}, {"g_V_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}}, {"g_V_dedup_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Dedup().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}}, {"g_V_hasLabelXsoftwareX_connectedComponent_project_byXnameX_byXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").ConnectedComponent().Project<object>("name", "component").By("name").By("gremlin.connectedComponentVertexProgram.component")}}, diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs index 884f666ff3..47bd8b4f79 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Process/Traversal/GremlinLangTests.cs @@ -1042,20 +1042,7 @@ namespace Gremlin.Net.UnitTest.Process.Traversal _g.V("1").Property(Cardinality.Set, map).GremlinLang.GetGremlin()); } - [Fact] - public void g_Inject_Char_a() - { - Assert.Equal("g.inject(\"a\"c)", - _g.Inject((object)'a').GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Char_DoubleQuote() - { - Assert.Equal("g.inject(\"\\\"\"c)", - _g.Inject((object)'"').GremlinLang.GetGremlin()); - } - + // Character - single quote (no feature equivalent) [Fact] public void g_Inject_Char_SingleQuote() { @@ -1063,48 +1050,7 @@ namespace Gremlin.Net.UnitTest.Process.Traversal _g.Inject((object)'\'').GremlinLang.GetGremlin()); } - [Fact] - public void g_Inject_Char_Backslash() - { - Assert.Equal("g.inject(\"\\\\\"c)", - _g.Inject((object)'\\').GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Char_Unicode() - { - Assert.Equal("g.inject(\"\\u00E9\"c)", - _g.Inject((object)'\u00e9').GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Duration_2H30M() - { - Assert.Equal("g.inject(Duration(\"PT2H30M\"))", - _g.Inject((object)new TimeSpan(2, 30, 0)).GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Duration_Zero() - { - Assert.Equal("g.inject(Duration(\"PT0S\"))", - _g.Inject((object)TimeSpan.Zero).GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Duration_Negative30S() - { - Assert.Equal("g.inject(Duration(\"-PT30S\"))", - _g.Inject((object)TimeSpan.FromSeconds(-30)).GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Duration_FractionalSeconds() - { - Assert.Equal("g.inject(Duration(\"PT0.5S\"))", - _g.Inject((object)TimeSpan.FromMilliseconds(500)).GremlinLang.GetGremlin()); - } - + // Duration - C#-specific: XmlConvert format with days, tick precision, negative prefix [Fact] public void g_Inject_Duration_DaysAndHours() { @@ -1126,26 +1072,5 @@ namespace Gremlin.Net.UnitTest.Process.Traversal Assert.Equal("g.inject(Duration(\"-PT0.5S\"))", _g.Inject((object)TimeSpan.FromMilliseconds(-500)).GremlinLang.GetGremlin()); } - - [Fact] - public void g_Inject_Binary() - { - Assert.Equal("g.inject(Binary(\"AQID\"))", - _g.Inject((object)new byte[] { 1, 2, 3 }).GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Binary_Empty() - { - Assert.Equal("g.inject(Binary(\"\"))", - _g.Inject((object)Array.Empty<byte>()).GremlinLang.GetGremlin()); - } - - [Fact] - public void g_Inject_Binary_Padding() - { - Assert.Equal("g.inject(Binary(\"AA==\"))", - _g.Inject((object)new byte[] { 0 }).GremlinLang.GetGremlin()); - } } } diff --git a/gremlin-go/build/generate.groovy b/gremlin-go/build/generate.groovy index 0ed3937d5f..39bc209713 100644 --- a/gremlin-go/build/generate.groovy +++ b/gremlin-go/build/generate.groovy @@ -83,7 +83,15 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> writer.write("\"") writer.write(k) writer.write("\": {") - def collected = v.collect { GremlinTranslator.translate(it, Translator.GO) } + def collected = v.collect { + try { + GremlinTranslator.translate(it, Translator.GO) + } catch (ignored) { + // fall back to the original gremlin-lang form for unsupported literals; + // the scenario will be skipped at test runtime via tag exclusions + GremlinTranslator.translate(it, Translator.LANGUAGE) + } + } def gremlinItty = collected.iterator() while (gremlinItty.hasNext()) { def t = gremlinItty.next() diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go index 87d1ecdae0..ef4921936a 100644 --- a/gremlin-go/driver/cucumber/cucumberSteps_test.go +++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go @@ -21,6 +21,7 @@ package gremlingo import ( "context" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -46,11 +47,15 @@ type tinkerPopGraph struct { var parsers map[*regexp.Regexp]func(string, string) interface{} +var iso8601DurationRe = regexp.MustCompile(`^(-?)PT(?:(-?\d+)H)?(?:(-?\d+)M)?(?:(-?\d+(?:\.\d+)?)S)?$`) + func init() { parsers = map[*regexp.Regexp]func(string, string) interface{}{ regexp.MustCompile(`^str\[(.*)]$`): func(stringVal, graphName string) interface{} { return stringVal }, //returns the string value as is regexp.MustCompile(`^dt\[(.*)]$`): toDateTime, regexp.MustCompile(`^uuid\[(.*)]$`): toUuid, + regexp.MustCompile(`^dur\[(.*)]$`): toDuration, + regexp.MustCompile(`^bin\[(.*)]$`): toBinary, regexp.MustCompile(`^d\[(.*)]\.[bslfd]$`): toNumeric, regexp.MustCompile(`^d\[(.*)]\.[m]$`): toBigDecimal, regexp.MustCompile(`^d\[(.*)]\.[n]$`): toBigInt, @@ -131,6 +136,51 @@ func toUuid(stringVal, graphName string) interface{} { return val } +// Parse duration from ISO-8601 string (e.g., "PT2H30M", "PT-30S"). +func toDuration(stringVal, graphName string) interface{} { + d, err := parseISO8601Duration(stringVal) + if err != nil { + return nil + } + return d +} + +// Parse binary from base64 string. +func toBinary(stringVal, graphName string) interface{} { + data, err := base64.StdEncoding.DecodeString(stringVal) + if err != nil { + return nil + } + return &gremlingo.ByteBuffer{Data: data} +} + +// parseISO8601Duration parses an ISO-8601 duration string (as produced by Java's Duration.toString()) +// into a time.Duration. Supports formats like "PT2H30M", "PT-30S", "PT0.5S". +func parseISO8601Duration(s string) (time.Duration, error) { + m := iso8601DurationRe.FindStringSubmatch(s) + if m == nil { + return 0, fmt.Errorf("invalid ISO-8601 duration: %s", s) + } + sign := time.Duration(1) + if m[1] == "-" { + sign = -1 + } + var d time.Duration + if m[2] != "" { + h, _ := strconv.ParseInt(m[2], 10, 64) + d += time.Duration(h) * time.Hour + } + if m[3] != "" { + min, _ := strconv.ParseInt(m[3], 10, 64) + d += time.Duration(min) * time.Minute + } + if m[4] != "" { + sec, _ := strconv.ParseFloat(m[4], 64) + d += time.Duration(sec * float64(time.Second)) + } + return sign * d, nil +} + // Parse numeric. func toNumeric(stringVal, graphName string) interface{} { if strings.Contains(stringVal, ".") { @@ -1014,7 +1064,7 @@ func TestCucumberFeatures(t *testing.T) { TestSuiteInitializer: InitializeTestSuite, ScenarioInitializer: InitializeScenario, Options: &godog.Options{ - Tags: "~@GraphComputerOnly && ~@AllowNullPropertyValues && ~@StepSubgraph && ~@StepTree && ~@StepWrite", + Tags: "~@GraphComputerOnly && ~@AllowNullPropertyValues && ~@StepSubgraph && ~@StepTree && ~@StepWrite && ~@DataChar", Format: "pretty", Paths: []string{getEnvOrDefaultString("CUCUMBER_FEATURE_FOLDER", "../../../gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features")}, TestingT: t, // Testing instance that will run subtests. diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index d42c088856..386ebb3df9 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -196,6 +196,11 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_V_valuesXintX_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_project_byXidentityX_byXmathXaddX999XXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("int", 50)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("int").AsNumber(gremlingo.GType.BigInt).Is(gremlingo.P.TypeOf(gremlingo.GType.BigInt)).Project("original", "added").By(gremlingo.T [...] "g_injectX777X_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_groupCount": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(777).AsNumber(gremlingo.GType.BigInt).Is(gremlingo.P.TypeOf(gremlingo.GType.BigInt)).GroupCount()}}, "g_V_valuesXageX_isXtypeOfXGType_BIGINTXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Is(gremlingo.P.TypeOf(gremlingo.GType.BigInt))}}, + "g_injectXBinaryXAQIDXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}})}}, + "g_injectXBinaryXemptyXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(gremlingo.ByteBuffer{Data: []byte{}})}}, + "g_injectXBinaryXAA_eqeqXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(gremlingo.ByteBuffer{Data: []byte{0}})}}, + "g_valuesXblobX_isXtypeOfXGType_BINARYXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("blob", gremlingo.ByteBuffer{Data: []byte{1,2,3}})}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("blob").Is(gremlingo.P.TypeOf(gremlingo.GType.Binary))}}, + "g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}}).Is(gremlingo.P.Eq(gremlingo.ByteBuffer{Data: []byte{1,2,3}}))}}, "g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("int", 5)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("int").AsNumber(gremlingo.GType.Byte).Is(gremlingo.P.TypeOf(gremlingo.GType.Byte))}}, "g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_mathXaddX20XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("int", 10)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("int").AsNumber(gremlingo.GType.Byte).Is(gremlingo.P.TypeOf(gremlingo.GType.Byte)).Math("_ + 20")}}, "g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_isXltX10XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("int", 7)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("int").AsNumber(gremlingo.GType.Byte).Is(gremlingo.P.TypeOf(gremlingo.GType.Byte)).Is(gremlingo.P.Lt(10))}}, @@ -204,6 +209,12 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_chooseXisXeqX12XX_constantXtwelveX_constantXotherXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("int", 12)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("int").AsNumber(gremlingo.GType.Byte).Is(gremlingo.P.TypeOf(gremlingo.GType.Byte)).Choose(gremlingo.T__.Is(gremlingo.P.Eq( [...] "g_injectX15X_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_groupCount": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(15).AsNumber(gremlingo.GType.Byte).Is(gremlingo.P.TypeOf(gremlingo.GType.Byte)).GroupCount()}}, "g_V_valuesXageX_isXtypeOfXGType_BYTEXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Is(gremlingo.P.TypeOf(gremlingo.GType.Byte))}}, + "g_injectXaX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.inject("a"c)}}, + "g_injectXescaped_quoteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.inject("\""c)}}, + "g_injectXescaped_backslashX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.inject("\\"c)}}, + "g_injectXunicodeX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.inject("\u00E9"c)}}, + "g_valuesXinitialX_isXtypeOfXGType_CHARXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.addV("data").property("initial", "a"c)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("initial").Is(gremlingo.P.TypeOf(gremlingo.GType.Char))}}, + "g_injectXaX_isXeqXaXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.inject("a"c).is(P.eq("a"c))}}, "g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("event").Property("datetime", time.Date(2023, 8, 8, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)))}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("datetime").Is(gremlingo.P.TypeOf(gremlingo.GType.DateTime))}}, "g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_project_byXidentityX_byXdateAddXDT_dayX1XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("event").Property("datetime", time.Date(2023, 8, 8, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)))}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("datetime").Is(gremlingo.P.TypeOf(gremlingo.GType.DateTime)).Project("orig [...] "g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_dateDiffXdatetimeX2023_08_10XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("event").Property("datetime", time.Date(2023, 8, 8, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)))}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("datetime").Is(gremlingo.P.TypeOf(gremlingo.GType.DateTime)).DateDiff(time.Date(2023, [...] @@ -223,6 +234,17 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_V_valuesXdoubleX_isXtypeOfXGType_DOUBLEXX_order_byXascX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("double", 3.2).AddV("data").Property("double", 1.8).AddV("data").Property("double", 2.5)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("double").Is(gremlingo.P.TypeOf(gremlingo.GType.Double)).Order().By(gremlingo.Order.Asc)}}, "g_injectX5_5dX_isXtypeOfXGType_DOUBLEXX_groupCount": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.5).Is(gremlingo.P.TypeOf(gremlingo.GType.Double)).GroupCount()}}, "g_V_valuesXageX_isXtypeOfXGType_DOUBLEXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Is(gremlingo.P.TypeOf(gremlingo.GType.Double))}}, + "g_injectXDurationXPT2H30MXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(9000000000000))}}, + "g_injectXDurationXPT0SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(0))}}, + "g_injectXDurationXPTneg30SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(-30000000000))}}, + "g_injectXDurationXnegPT30SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(-30000000000))}}, + "g_injectXDurationXPT0_5SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(500000000))}}, + "g_injectXDurationXP1DT12HXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(129600000000000))}}, + "g_injectXDurationXP2DXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(172800000000000))}}, + "g_injectXDurationXPT1H30M15SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(5415000000000))}}, + "g_injectXDurationXPTneg0_5SXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(-500000000))}}, + "g_valuesXlengthX_isXtypeOfXGType_DURATIONXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("length", time.Duration(9000000000000))}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("length").Is(gremlingo.P.TypeOf(gremlingo.GType.Duration))}}, + "g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(time.Duration(9000000000000)).Is(gremlingo.P.Gt(time.Duration(3600000000000)))}}, "g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("float", 2.5)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("float").AsNumber(gremlingo.GType.Float).Is(gremlingo.P.TypeOf(gremlingo.GType.Float))}}, "g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_mathXmulX2XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("float", 3.0)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("float").AsNumber(gremlingo.GType.Float).Is(gremlingo.P.TypeOf(gremlingo.GType.Float)).Math("_ * 2")}}, "g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_isXeqX1_5XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("data").Property("float", 1.5)}, func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("float").AsNumber(gremlingo.GType.Float).Is(gremlingo.P.TypeOf(gremlingo.GType.Float)).Is(gremlingo.P.Eq(1.5))}}, diff --git a/gremlin-go/driver/gremlinlang_test.go b/gremlin-go/driver/gremlinlang_test.go index 9f31595113..6e5f5d98cf 100644 --- a/gremlin-go/driver/gremlinlang_test.go +++ b/gremlin-go/driver/gremlinlang_test.go @@ -680,79 +680,45 @@ func Test_GremlinLang(t *testing.T) { }, equals: "g.V().has(\"name\",\"\\\"marko\\n\\r\\t\\b\\f\\\"\")", }, + // Duration - Go-specific: nanosecond precision, negative sub-second, days+hours normalization { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(2*time.Hour + 30*time.Minute) - }, - equals: `g.inject(Duration("PT2H30M"))`, - }, - { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(time.Duration(0)) - }, - equals: `g.inject(Duration("PT0S"))`, - }, - { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(-30 * time.Second) - }, - equals: `g.inject(Duration("PT-30S"))`, - }, - { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(500 * time.Millisecond) - }, - equals: `g.inject(Duration("PT0.5S"))`, - }, - { + name: "g_Inject_Duration_NanosecondPrecision", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject(time.Nanosecond) }, equals: `g.inject(Duration("PT0.000000001S"))`, }, { + name: "g_Inject_Duration_NegativeSubSecond", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject(-500 * time.Millisecond) }, equals: `g.inject(Duration("PT-0.5S"))`, }, { + name: "g_Inject_Duration_DaysAndHours", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject(36 * time.Hour) }, equals: `g.inject(Duration("PT36H"))`, }, + // Binary - []byte type (distinct from ByteBuffer) { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(ByteBuffer{Data: []byte{1, 2, 3}}) - }, - equals: `g.inject(Binary("AQID"))`, - }, - { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(ByteBuffer{Data: []byte{}}) - }, - equals: `g.inject(Binary(""))`, - }, - { - assert: func(g *GraphTraversalSource) *GraphTraversal { - return g.Inject(ByteBuffer{Data: []byte{0}}) - }, - equals: `g.inject(Binary("AA=="))`, - }, - { + name: "g_Inject_ByteSlice", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject([]byte{1, 2, 3}) }, equals: `g.inject(Binary("AQID"))`, }, { + name: "g_Inject_ByteSlice_Empty", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject([]byte{}) }, equals: `g.inject(Binary(""))`, }, { + name: "g_Inject_ByteSlice_Padding", assert: func(g *GraphTraversalSource) *GraphTraversal { return g.Inject([]byte{0}) }, diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/AnonymizedTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/AnonymizedTranslateVisitor.ts index 4f90e986fa..3ff98f0288 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/AnonymizedTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/AnonymizedTranslateVisitor.ts @@ -141,6 +141,18 @@ export default class AnonymizedTranslateVisitor extends TranslateVisitor { this.anonymize(ctx, 'String'); } + visitCharacterLiteral(ctx: any): void { + this.anonymize(ctx, 'Character'); + } + + visitDurationLiteral(ctx: any): void { + this.anonymize(ctx, 'Duration'); + } + + visitBinaryLiteral(ctx: any): void { + this.anonymize(ctx, 'ByteBuffer'); + } + // ─── Keys — output unquoted (only values are anonymized) ───────────────── visitNakedKey(ctx: any): void { diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/DotNetTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/DotNetTranslateVisitor.ts index 11e303144f..247f54af3f 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/DotNetTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/DotNetTranslateVisitor.ts @@ -225,6 +225,27 @@ export default class DotNetTranslateVisitor extends TranslateVisitor { this.sb.push(')'); } + visitCharacterLiteral(ctx: any): void { + const text: string = ctx.getText(); + const withoutSuffix = text.substring(0, text.length - 1); + const inner = TranslateVisitor.removeFirstAndLastCharacters(withoutSuffix); + this.sb.push("'"); + this.sb.push(inner); + this.sb.push("'"); + } + + visitDurationLiteral(ctx: any): void { + this.sb.push('XmlConvert.ToTimeSpan('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push(')'); + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push('Convert.FromBase64String('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push(')'); + } + visitClassType(ctx: any): void { this.sb.push('typeof('); this.sb.push(ctx.getText()); diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/GoTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/GoTranslateVisitor.ts index a3806bca85..9bfc87c2c4 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/GoTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/GoTranslateVisitor.ts @@ -22,6 +22,7 @@ import TranslateVisitor from './TranslateVisitor.js'; import { TranslatorException } from './TranslatorException.js'; +import { Buffer } from 'buffer'; const GO_PACKAGE_NAME = 'gremlingo.'; @@ -256,6 +257,27 @@ export default class GoTranslateVisitor extends TranslateVisitor { this.sb.push(')'); } + visitCharacterLiteral(_ctx: any): void { + throw new TranslatorException('Character literals are not supported in Go'); + } + + visitDurationLiteral(ctx: any): void { + const iso8601 = TranslateVisitor.removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + const nanos = parseISO8601DurationToNanos(iso8601); + this.sb.push(`time.Duration(${nanos})`); + } + + visitBinaryLiteral(ctx: any): void { + const base64Str = TranslateVisitor.removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + const bytes = base64ToBytes(base64Str); + this.sb.push(GO_PACKAGE_NAME + 'ByteBuffer{Data: []byte{'); + for (let i = 0; i < bytes.length; i++) { + if (i > 0) this.sb.push(','); + this.sb.push(String(bytes[i])); + } + this.sb.push('}}'); + } + visitTraversalStrategy(ctx: any): void { if (ctx.getChildCount() === 1) { this.sb.push(GO_PACKAGE_NAME); @@ -349,6 +371,51 @@ export default class GoTranslateVisitor extends TranslateVisitor { } } +/** + * Parses an ISO-8601 duration string into total nanoseconds, + * matching the Java GoTranslateVisitor behavior. + */ +function parseISO8601DurationToNanos(iso8601: string): number { + const match = iso8601.match(/^(-?)P(?:(\d+)D)?T?(?:(-?\d+)H)?(?:(-?\d+)M)?(?:(-?\d+(?:\.\d+)?)S)?$/); + if (!match) { + throw new TranslatorException(`Invalid ISO-8601 duration: ${iso8601}`); + } + const negative = match[1] === '-'; + const days = match[2] ? parseInt(match[2], 10) : 0; + const hours = match[3] ? parseInt(match[3], 10) : 0; + const minutes = match[4] ? parseInt(match[4], 10) : 0; + + let totalNanos = (days * 86400 + hours * 3600 + minutes * 60) * 1_000_000_000; + + if (match[5]) { + const secParts = match[5].split('.'); + totalNanos += parseInt(secParts[0], 10) * 1_000_000_000; + if (secParts.length > 1) { + const fracStr = secParts[1].padEnd(9, '0').substring(0, 9); + let fracNanos = parseInt(fracStr, 10); + if (parseInt(secParts[0], 10) < 0 || (parseInt(secParts[0], 10) === 0 && match[5].startsWith('-'))) { + fracNanos = -fracNanos; + } + totalNanos += fracNanos; + } + } + + return negative ? -totalNanos : totalNanos; +} + +/** + * Decodes a base64 string into an array of unsigned byte values. + */ +function base64ToBytes(base64: string): number[] { + if (base64 === '') return []; + const buf = Buffer.from(base64, 'base64'); + const bytes: number[] = new Array(buf.length); + for (let i = 0; i < buf.length; i++) { + bytes[i] = buf[i]; + } + return bytes; +} + class SymbolHelper { private static readonly TO_GO_MAP: Record<string, string> = { 'OUT': 'Out', diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/GroovyTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/GroovyTranslateVisitor.ts index 4058377cd2..d77946da39 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/GroovyTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/GroovyTranslateVisitor.ts @@ -114,6 +114,27 @@ export default class GroovyTranslateVisitor extends TranslateVisitor { this.sb.push(')'); } + visitCharacterLiteral(ctx: any): void { + const text: string = ctx.getText(); + const withoutSuffix = text.substring(0, text.length - 1); + const inner = TranslateVisitor.removeFirstAndLastCharacters(withoutSuffix); + this.sb.push("'"); + this.sb.push(inner); + this.sb.push("' as char"); + } + + visitDurationLiteral(ctx: any): void { + this.sb.push('Duration.parse('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push(')'); + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push('ByteBuffer.wrap(Base64.getDecoder().decode('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push('))'); + } + visitNullLiteral(ctx: any): void { if (ctx.parent?.constructor?.name === 'GenericMapNullableArgumentContext') { this.sb.push('null as Map'); diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/JavaTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/JavaTranslateVisitor.ts index b7cf670f3d..414b76b6aa 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/JavaTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/JavaTranslateVisitor.ts @@ -200,6 +200,27 @@ export default class JavaTranslateVisitor extends TranslateVisitor { this.sb.push(')'); } + visitCharacterLiteral(ctx: any): void { + const text: string = ctx.getText(); + const withoutSuffix = text.substring(0, text.length - 1); + const inner = TranslateVisitor.removeFirstAndLastCharacters(withoutSuffix); + this.sb.push("'"); + this.sb.push(inner); + this.sb.push("'"); + } + + visitDurationLiteral(ctx: any): void { + this.sb.push('Duration.parse('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push(')'); + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push('ByteBuffer.wrap(Base64.getDecoder().decode('); + this.sb.push(ctx.stringLiteral().getText()); + this.sb.push('))'); + } + visitGenericRangeLiteral(_ctx: any): void { throw new TranslatorException('Java does not support range literals'); } diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/JavascriptTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/JavascriptTranslateVisitor.ts index e01fb5568b..4ae5020ba1 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/JavascriptTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/JavascriptTranslateVisitor.ts @@ -185,6 +185,20 @@ export default class JavascriptTranslateVisitor extends TranslateVisitor { this.visitStringLiteral(ctx.stringLiteral()); this.sb.push(')'); } + + visitCharacterLiteral(_ctx: any): void { + throw new TranslatorException('Character literals are not supported in JavaScript'); + } + + visitDurationLiteral(_ctx: any): void { + throw new TranslatorException('Duration literals are not supported in JavaScript'); + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push("Buffer.from("); + this.visitStringLiteral(ctx.stringLiteral()); + this.sb.push(",'base64')"); + } } /** diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/PythonTranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/PythonTranslateVisitor.ts index e74eb549c8..f8006a3f60 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/PythonTranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/PythonTranslateVisitor.ts @@ -214,6 +214,32 @@ export default class PythonTranslateVisitor extends TranslateVisitor { this.sb.push(')'); } + visitCharacterLiteral(ctx: any): void { + const text: string = ctx.getText(); + const withoutSuffix = text.substring(0, text.length - 1); + const inner = TranslateVisitor.removeFirstAndLastCharacters(withoutSuffix); + this.sb.push("SingleChar('"); + this.sb.push(inner); + this.sb.push("')"); + } + + visitDurationLiteral(ctx: any): void { + const iso8601 = TranslateVisitor.removeFirstAndLastCharacters(ctx.stringLiteral().getText()); + const { totalSeconds, nanos } = parseISO8601Duration(iso8601); + if (nanos === 0) { + this.sb.push(`timedelta(seconds=${totalSeconds})`); + } else { + const micros = Math.floor(nanos / 1000); + this.sb.push(`timedelta(seconds=${totalSeconds},microseconds=${micros})`); + } + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push('base64.b64decode('); + this.visitStringLiteral(ctx.stringLiteral()); + this.sb.push(')'); + } + visitTraversalStrategy(ctx: any): void { if (ctx.getChildCount() === 1) { this.sb.push(ctx.getText()); @@ -319,3 +345,46 @@ function convertCamelCaseToSnakeCase(camelCase: string): string { } return result; } + +/** + * Parses an ISO-8601 duration string (as produced by Java's Duration.toString()) + * into total seconds and nanoseconds, matching the Java PythonTranslateVisitor behavior. + */ +function parseISO8601Duration(iso8601: string): { totalSeconds: number; nanos: number } { + // Use a regex that matches Java's Duration.toString() output + const match = iso8601.match(/^(-?)P(?:(\d+)D)?T?(?:(-?\d+)H)?(?:(-?\d+)M)?(?:(-?\d+(?:\.\d+)?)S)?$/); + if (!match) { + throw new TranslatorException(`Invalid ISO-8601 duration: ${iso8601}`); + } + const negative = match[1] === '-'; + const days = match[2] ? parseInt(match[2], 10) : 0; + const hours = match[3] ? parseInt(match[3], 10) : 0; + const minutes = match[4] ? parseInt(match[4], 10) : 0; + + let totalSeconds = days * 86400 + hours * 3600 + minutes * 60; + let nanos = 0; + + if (match[5]) { + const secParts = match[5].split('.'); + totalSeconds += parseInt(secParts[0], 10); + if (secParts.length > 1) { + nanos = parseInt(secParts[1].padEnd(9, '0').substring(0, 9), 10); + if (parseInt(secParts[0], 10) < 0 || (parseInt(secParts[0], 10) === 0 && match[5].startsWith('-'))) { + nanos = -nanos; + } + } + } + + if (negative) { + totalSeconds = -totalSeconds; + nanos = -nanos; + } + + // Normalize so nanos is always 0..999999999 (matching Java's Duration representation) + if (nanos < 0) { + totalSeconds -= 1; + nanos += 1_000_000_000; + } + + return { totalSeconds, nanos }; +} diff --git a/gremlin-js/gremlin-javascript/lib/language/translator/TranslateVisitor.ts b/gremlin-js/gremlin-javascript/lib/language/translator/TranslateVisitor.ts index dcf2b77aa1..207a53c40a 100644 --- a/gremlin-js/gremlin-javascript/lib/language/translator/TranslateVisitor.ts +++ b/gremlin-js/gremlin-javascript/lib/language/translator/TranslateVisitor.ts @@ -394,6 +394,18 @@ export default class TranslateVisitor { this.sb.push(ctx.getText()); } + visitCharacterLiteral(ctx: any): void { + this.sb.push(ctx.getText()); + } + + visitDurationLiteral(ctx: any): void { + this.sb.push(ctx.getText()); + } + + visitBinaryLiteral(ctx: any): void { + this.sb.push(ctx.getText()); + } + visitStringLiteral(ctx: any): void { const text = TranslateVisitor.removeFirstAndLastCharacters(ctx.getText()); this.handleStringLiteralText(text); diff --git a/gremlin-js/gremlin-javascript/scripts/groovy/generate.groovy b/gremlin-js/gremlin-javascript/scripts/groovy/generate.groovy index 94824d3331..bfde0e275b 100644 --- a/gremlin-js/gremlin-javascript/scripts/groovy/generate.groovy +++ b/gremlin-js/gremlin-javascript/scripts/groovy/generate.groovy @@ -106,7 +106,15 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> writer.write(" ") writer.write(k) writer.write(": [") - def collected = v.collect { GremlinTranslator.translate(it, Translator.JAVASCRIPT) } + def collected = v.collect { + try { + GremlinTranslator.translate(it, Translator.JAVASCRIPT) + } catch (ignored) { + // fall back to the original gremlin-lang form for unsupported literals; + // the scenario will be skipped at test runtime via tag exclusions + GremlinTranslator.translate(it, Translator.LANGUAGE) + } + } def uniqueBindings = collected.collect { it.getParameters() }.flatten().unique() def gremlinItty = collected.iterator() while (gremlinItty.hasNext()) { diff --git a/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js index e8ac2d1c1c..08830a407e 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/feature-steps.js @@ -36,6 +36,7 @@ import { toLong } from '../../lib/utils.js'; import anon from '../../lib/process/anonymous-traversal.js'; const __ = statics; import { deepMembersById } from './element-comparison.js'; +import { Buffer } from 'buffer'; import GremlinLang from "../../lib/process/gremlin-lang.js"; const parsers = [ [ 'str\\[(.*)\\]', (stringValue) => stringValue ], //returns the string value as is @@ -44,6 +45,7 @@ const parsers = [ [ 'dt\\[(.+)\\]', toDateTime ], [ 'uuid\\[(.+)\\]', toUuid ], [ 'd\\[(.*)\\]\\.[bsilfdmn]', toNumeric ], + [ 'bin\\[(.*)\\]', toBinary ], [ 'v\\[(.+)\\]', toVertex ], [ 'v\\[(.+)\\]\\.id', toVertexId ], [ 'v\\[(.+)\\]\\.sid', toVertexIdString ], @@ -448,6 +450,10 @@ function toMerge(value) { return merge[value]; } +function toBinary(value) { + return Buffer.from(value, 'base64'); +} + function splitByElement(s) { let depth = 0; let current = ''; diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js index 0bb0a484a4..877d893312 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js @@ -227,6 +227,11 @@ const gremlins = { g_V_valuesXintX_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_project_byXidentityX_byXmathXaddX999XXX: [function({g}) { return g.addV("data").property("int", 50) }, function({g}) { return g.V().values("int").asNumber(GType.bigInt).is(P.typeOf(GType.bigInt)).project("original", "added").by(__.identity()).by(__.math("_ + 999")) }], g_injectX777X_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_groupCount: [function({g}) { return g.inject(777).asNumber(GType.bigInt).is(P.typeOf(GType.bigInt)).groupCount() }], g_V_valuesXageX_isXtypeOfXGType_BIGINTXX: [function({g}) { return g.V().values("age").is(P.typeOf(GType.bigInt)) }], + g_injectXBinaryXAQIDXX: [function({g}) { return g.inject(Buffer.from("AQID",'base64')) }], + g_injectXBinaryXemptyXX: [function({g}) { return g.inject(Buffer.from("",'base64')) }], + g_injectXBinaryXAA_eqeqXX: [function({g}) { return g.inject(Buffer.from("AA==",'base64')) }], + g_valuesXblobX_isXtypeOfXGType_BINARYXX: [function({g}) { return g.addV("data").property("blob", Buffer.from("AQID",'base64')) }, function({g}) { return g.V().values("blob").is(P.typeOf(GType.binary)) }], + g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX: [function({g}) { return g.inject(Buffer.from("AQID",'base64')).is(P.eq(Buffer.from("AQID",'base64'))) }], g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX: [function({g}) { return g.addV("data").property("int", 5) }, function({g}) { return g.V().values("int").asNumber(GType.byte).is(P.typeOf(GType.byte)) }], g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_mathXaddX20XX: [function({g}) { return g.addV("data").property("int", 10) }, function({g}) { return g.V().values("int").asNumber(GType.byte).is(P.typeOf(GType.byte)).math("_ + 20") }], g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_isXltX10XX: [function({g}) { return g.addV("data").property("int", 7) }, function({g}) { return g.V().values("int").asNumber(GType.byte).is(P.typeOf(GType.byte)).is(P.lt(10)) }], @@ -235,6 +240,12 @@ const gremlins = { g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_chooseXisXeqX12XX_constantXtwelveX_constantXotherXX: [function({g}) { return g.addV("data").property("int", 12) }, function({g}) { return g.V().values("int").asNumber(GType.byte).is(P.typeOf(GType.byte)).choose(__.is(P.eq(12)), __.constant("twelve"), __.constant("other")) }], g_injectX15X_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_groupCount: [function({g}) { return g.inject(15).asNumber(GType.byte).is(P.typeOf(GType.byte)).groupCount() }], g_V_valuesXageX_isXtypeOfXGType_BYTEXX: [function({g}) { return g.V().values("age").is(P.typeOf(GType.byte)) }], + g_injectXaX: [function({g}) { return g.inject("a"c) }], + g_injectXescaped_quoteX: [function({g}) { return g.inject("\""c) }], + g_injectXescaped_backslashX: [function({g}) { return g.inject("\\"c) }], + g_injectXunicodeX: [function({g}) { return g.inject("\u00E9"c) }], + g_valuesXinitialX_isXtypeOfXGType_CHARXX: [function({g}) { return g.addV("data").property("initial", "a"c) }, function({g}) { return g.V().values("initial").is(P.typeOf(GType.char)) }], + g_injectXaX_isXeqXaXX: [function({g}) { return g.inject("a"c).is(P.eq("a"c)) }], g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX: [function({g}) { return g.addV("event").property("datetime", new Date('2023-08-08T00:00Z')) }, function({g}) { return g.V().values("datetime").is(P.typeOf(GType.datetime)) }], g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_project_byXidentityX_byXdateAddXDT_dayX1XX: [function({g}) { return g.addV("event").property("datetime", new Date('2023-08-08T00:00Z')) }, function({g}) { return g.V().values("datetime").is(P.typeOf(GType.datetime)).project("original", "nextDay").by(__.identity()).by(__.dateAdd(DT.day, 1)) }], g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_dateDiffXdatetimeX2023_08_10XX: [function({g}) { return g.addV("event").property("datetime", new Date('2023-08-08T00:00Z')) }, function({g}) { return g.V().values("datetime").is(P.typeOf(GType.datetime)).dateDiff(new Date('2023-08-08T00:00:30Z')) }], @@ -254,6 +265,17 @@ const gremlins = { g_V_valuesXdoubleX_isXtypeOfXGType_DOUBLEXX_order_byXascX: [function({g}) { return g.addV("data").property("double", 3.2).addV("data").property("double", 1.8).addV("data").property("double", 2.5) }, function({g}) { return g.V().values("double").is(P.typeOf(GType.double)).order().by(Order.asc) }], g_injectX5_5dX_isXtypeOfXGType_DOUBLEXX_groupCount: [function({g}) { return g.inject(5.5).is(P.typeOf(GType.double)).groupCount() }], g_V_valuesXageX_isXtypeOfXGType_DOUBLEXX: [function({g}) { return g.V().values("age").is(P.typeOf(GType.double)) }], + g_injectXDurationXPT2H30MXX: [function({g}) { return g.inject(Duration("PT2H30M")) }], + g_injectXDurationXPT0SXX: [function({g}) { return g.inject(Duration("PT0S")) }], + g_injectXDurationXPTneg30SXX: [function({g}) { return g.inject(Duration("PT-30S")) }], + g_injectXDurationXnegPT30SXX: [function({g}) { return g.inject(Duration("-PT30S")) }], + g_injectXDurationXPT0_5SXX: [function({g}) { return g.inject(Duration("PT0.5S")) }], + g_injectXDurationXP1DT12HXX: [function({g}) { return g.inject(Duration("P1DT12H")) }], + g_injectXDurationXP2DXX: [function({g}) { return g.inject(Duration("P2D")) }], + g_injectXDurationXPT1H30M15SXX: [function({g}) { return g.inject(Duration("PT1H30M15S")) }], + g_injectXDurationXPTneg0_5SXX: [function({g}) { return g.inject(Duration("PT-0.5S")) }], + g_valuesXlengthX_isXtypeOfXGType_DURATIONXX: [function({g}) { return g.addV("data").property("length", Duration("PT2H30M")) }, function({g}) { return g.V().values("length").is(P.typeOf(GType.duration)) }], + g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX: [function({g}) { return g.inject(Duration("PT2H30M")).is(P.gt(Duration("PT1H"))) }], g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX: [function({g}) { return g.addV("data").property("float", 2.5) }, function({g}) { return g.V().values("float").asNumber(GType.float).is(P.typeOf(GType.float)) }], g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_mathXmulX2XX: [function({g}) { return g.addV("data").property("float", 3.0) }, function({g}) { return g.V().values("float").asNumber(GType.float).is(P.typeOf(GType.float)).math("_ * 2") }], g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_isXeqX1_5XX: [function({g}) { return g.addV("data").property("float", 1.5) }, function({g}) { return g.V().values("float").asNumber(GType.float).is(P.typeOf(GType.float)).is(P.eq(1.5)) }], diff --git a/gremlin-js/gremlin-javascript/test/cucumber/world.js b/gremlin-js/gremlin-javascript/test/cucumber/world.js index 1e66c8e73b..563abb8ce8 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/world.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/world.js @@ -110,6 +110,14 @@ Before({tags: "@StepWrite"}, function() { return 'skipped' }) +Before({tags: "@DataChar"}, function() { + return 'skipped' +}) + +Before({tags: "@DataDuration"}, function() { + return 'skipped' +}) + function getVertices(connection) { const g = anon.traversal().withRemote(connection); return g.V().group().by('name').by(__.tail()).next().then(it => { diff --git a/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js b/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js index 59197e14c3..be3a39c460 100644 --- a/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js +++ b/gremlin-js/gremlin-javascript/test/unit/gremlin-lang-test.js @@ -415,18 +415,7 @@ describe('GremlinLang', function () { ); }); - it('should handle Buffer', function () { - assert.strictEqual(g.inject(Buffer.from([1, 2, 3])).getGremlinLang().getGremlin(), 'g.inject(Binary("AQID"))'); - }); - - it('should handle empty Buffer', function () { - assert.strictEqual(g.inject(Buffer.from([])).getGremlinLang().getGremlin(), 'g.inject(Binary(""))'); - }); - - it('should handle Buffer with base64 padding', function () { - assert.strictEqual(g.inject(Buffer.from([0])).getGremlinLang().getGremlin(), 'g.inject(Binary("AA=="))'); - }); - + // Binary - Uint8Array type (JS-specific, distinct from Buffer) it('should handle Uint8Array', function () { assert.strictEqual(g.inject(new Uint8Array([1, 2, 3])).getGremlinLang().getGremlin(), 'g.inject(Binary("AQID"))'); }); diff --git a/gremlin-js/gremlin-javascript/test/unit/translator/gremlin-translator-test.js b/gremlin-js/gremlin-javascript/test/unit/translator/gremlin-translator-test.js index 02866c3b37..4fddec1343 100644 --- a/gremlin-js/gremlin-javascript/test/unit/translator/gremlin-translator-test.js +++ b/gremlin-js/gremlin-javascript/test/unit/translator/gremlin-translator-test.js @@ -64,6 +64,8 @@ describe('GremlinTranslator', function () { ['g.inject(NaN)', 'g.inject(Number.NaN)'], ['g.inject(Infinity)', 'g.inject(Number.POSITIVE_INFINITY)'], ['g.inject(-Infinity)', 'g.inject(Number.NEGATIVE_INFINITY)'], + // Binary literal + ['g.inject(Binary("AQID"))', 'g.inject(Buffer.from("AQID",\'base64\'))'], // terminal steps — pass through as-is in JavaScript ['g.V().toList()', 'g.V().toList()'], ['g.V().toSet()', 'g.V().toSet()'], @@ -134,6 +136,20 @@ describe('GremlinTranslator', function () { TranslatorException, ); }); + + it('throws TranslatorException for character literal', function () { + assert.throws( + () => GremlinTranslator.translate('g.inject("a"c)'), + TranslatorException, + ); + }); + + it('throws TranslatorException for duration literal', function () { + assert.throws( + () => GremlinTranslator.translate('g.inject(Duration("PT2H30M"))'), + TranslatorException, + ); + }); }); }); @@ -180,6 +196,12 @@ describe('PythonTranslateVisitor', function () { ['g.inject([1,2,3])', 'g.inject([1, 2, 3])'], // Set literal (non-empty) ['g.inject({1,2,3})', 'g.inject({1, 2, 3})'], + // Character literal + ['g.inject("a"c)', "g.inject(SingleChar('a'))"], + // Duration literal + ['g.inject(Duration("PT2H30M"))', 'g.inject(timedelta(seconds=9000))'], + // Binary literal + ['g.inject(Binary("AQID"))', "g.inject(base64.b64decode('AQID'))"], // Map literal — T.id → T.id_ in Python ['g.addV("person").property(T.id, [(T.label): "person"])', "g.add_v('person').property(T.id_, { T.label: 'person' })"], // terminal steps — camelCase → snake_case @@ -259,6 +281,10 @@ describe('GoTranslateVisitor', function () { ['g.inject(NaN)', 'g.Inject(math.NaN())'], ['g.inject(Infinity)', 'g.Inject(math.Inf(1))'], ['g.inject(-Infinity)', 'g.Inject(math.Inf(-1))'], + // Duration literal + ['g.inject(Duration("PT2H30M"))', 'g.Inject(time.Duration(9000000000000))'], + // Binary literal + ['g.inject(Binary("AQID"))', 'g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}})'], // null → nil ['g.V().has("name", null)', 'g.V().Has("name", nil)'], // terminal steps — PascalCase @@ -302,6 +328,13 @@ describe('GoTranslateVisitor', function () { TranslatorException, ); }); + + it('throws TranslatorException for character literal', function () { + assert.throws( + () => GremlinTranslator.translate('g.inject("a"c)', 'g', 'GO'), + TranslatorException, + ); + }); }); }); @@ -336,6 +369,12 @@ describe('DotNetTranslateVisitor', function () { // Collections ['g.inject([1,2,3])', 'g.Inject<object>(new List<object> { 1, 2, 3 })'], ['g.inject({1,2,3})', 'g.Inject<object>(new HashSet<object> { 1, 2, 3 })'], + // Character literal + ['g.inject("a"c)', "g.Inject<object>('a')"], + // Duration literal + ['g.inject(Duration("PT2H30M"))', 'g.Inject<object>(XmlConvert.ToTimeSpan("PT2H30M"))'], + // Binary literal + ['g.inject(Binary("AQID"))', 'g.Inject<object>(Convert.FromBase64String("AQID"))'], // Map literal ['g.addV("person").property(T.id, [(T.label): "person"])', 'g.AddV((string) "person").Property(T.Id, new Dictionary<object, object> {{ T.Label, "person" }})'], // values gets <object> @@ -421,6 +460,12 @@ describe('JavaTranslateVisitor', function () { ['g.inject([1,2,3])', 'g.inject(new ArrayList<Object>() {{ add(1); add(2); add(3); }})'], // Set literal ['g.inject({1,2,3})', 'g.inject(new HashSet<Object>() {{ add(1); add(2); add(3); }})'], + // Character literal + ['g.inject("a"c)', "g.inject('a')"], + // Duration literal + ['g.inject(Duration("PT2H30M"))', 'g.inject(Duration.parse("PT2H30M"))'], + // Binary literal + ['g.inject(Binary("AQID"))', 'g.inject(ByteBuffer.wrap(Base64.getDecoder().decode("AQID")))'], // Map literal ['g.addV("person").property(T.id, [(T.label): "person"])', 'g.addV("person").property(T.id, new LinkedHashMap<Object, Object>() {{ put(T.label, "person"); }})'], // terminal steps — pass through as-is in Java @@ -499,6 +544,12 @@ describe('GroovyTranslateVisitor', function () { ['g.inject([1,2,3])', 'g.inject([1, 2, 3])'], // Set literal — Groovy: [items] as Set ['g.inject({1,2,3})', 'g.inject([1, 2, 3] as Set)'], + // Character literal + ['g.inject("a"c)', "g.inject('a' as char)"], + // Duration literal + ['g.inject(Duration("PT2H30M"))', 'g.inject(Duration.parse("PT2H30M"))'], + // Binary literal + ['g.inject(Binary("AQID"))', 'g.inject(ByteBuffer.wrap(Base64.getDecoder().decode("AQID")))'], // Map literal — Groovy preserves parens on expression keys ['g.addV("person").property(T.id, [(T.label): "person"])', 'g.addV("person").property(T.id, [(T.label):"person"])'], // null as Map in mergeV @@ -584,6 +635,12 @@ describe('AnonymizedTranslateVisitor', function () { ['g.inject([1,2,3])', 'g.inject(list0)'], // set → set ['g.inject({1,2,3})', 'g.inject(set0)'], + // character → character + ['g.inject("a"c)', 'g.inject(character0)'], + // duration → duration + ['g.inject(Duration("PT2H30M"))', 'g.inject(duration0)'], + // binary → bytebuffer + ['g.inject(Binary("AQID"))', 'g.inject(bytebuffer0)'], // map → map ['g.addV("person").property(T.id, [(T.label): "person"])', 'g.addV(string0).property(T.id, map0)'], // enums/traversal steps pass through unchanged diff --git a/gremlin-python/src/main/python/tests/feature/feature_steps.py b/gremlin-python/src/main/python/tests/feature/feature_steps.py index f82b65ab55..7ed183413c 100644 --- a/gremlin-python/src/main/python/tests/feature/feature_steps.py +++ b/gremlin-python/src/main/python/tests/feature/feature_steps.py @@ -18,11 +18,12 @@ # from collections.abc import Iterable -from datetime import datetime +from datetime import datetime, timedelta +import base64 import json import re import uuid -from gremlin_python.statics import long, bigdecimal +from gremlin_python.statics import long, bigdecimal, SingleChar from gremlin_python.structure.graph import Path, Vertex, Graph, Edge, VertexProperty, Property from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.process.graph_traversal import __ @@ -345,6 +346,18 @@ def _split_by_element(s): return results +def _parse_iso8601_duration(iso_str): + """Parses an ISO-8601 duration string (as produced by Java's Duration.toString()) into a timedelta.""" + m = re.match(r'^(-?)PT(?:(-?\d+)H)?(?:(-?\d+)M)?(?:(-?\d+(?:\.\d+)?)S)?$', iso_str) + if not m: + raise ValueError("Invalid ISO-8601 duration: " + iso_str) + sign = -1 if m.group(1) == '-' else 1 + hours = int(m.group(2)) if m.group(2) else 0 + minutes = int(m.group(3)) if m.group(3) else 0 + seconds = float(m.group(4)) if m.group(4) else 0.0 + return timedelta(hours=sign * hours, minutes=sign * minutes, seconds=sign * seconds) + + def _convert(val, ctx): graph_name = ctx.graph_name if isinstance(val, dict): # convert dictionary keys/values @@ -366,6 +379,12 @@ def _convert(val, ctx): elif isinstance(val, str) and re.match(r"^uuid\[.*\]$", val): # parse uuid name = val[5:-1] # strip 'uuid[...]' or similar format return uuid.UUID(name) + elif isinstance(val, str) and re.match(r"^char\[.\]$", val): # parse char + return SingleChar(val[5:-1]) + elif isinstance(val, str) and re.match(r"^dur\[.*\]$", val): # parse duration + return _parse_iso8601_duration(val[4:-1]) + elif isinstance(val, str) and re.match(r"^bin\[.*\]$", val): # parse binary + return base64.b64decode(val[4:-1]) elif isinstance(val, str) and re.match(r"^d\[NaN\]$", val): # parse nan return float("nan") elif isinstance(val, str) and re.match(r"^d\[Infinity\]$", val): # parse +inf diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py index 235b5f9384..d1c1d1c70e 100644 --- a/gremlin-python/src/main/python/tests/feature/gremlin.py +++ b/gremlin-python/src/main/python/tests/feature/gremlin.py @@ -199,6 +199,11 @@ world.gremlins = { 'g_V_valuesXintX_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_project_byXidentityX_byXmathXaddX999XXX': [(lambda g:g.add_v('data').property('int', 50)), (lambda g:g.V().values('int').as_number(GType.BIGINT).is_(P.type_of(GType.BIGINT)).project('original', 'added').by(__.identity()).by(__.math('_ + 999')))], 'g_injectX777X_asNumberXGType_BIGINTX_isXtypeOfXGType_BIGINTXX_groupCount': [(lambda g:g.inject(777).as_number(GType.BIGINT).is_(P.type_of(GType.BIGINT)).group_count())], 'g_V_valuesXageX_isXtypeOfXGType_BIGINTXX': [(lambda g:g.V().values('age').is_(P.type_of(GType.BIGINT)))], + 'g_injectXBinaryXAQIDXX': [(lambda g:g.inject(base64.b64decode('AQID')))], + 'g_injectXBinaryXemptyXX': [(lambda g:g.inject(base64.b64decode('')))], + 'g_injectXBinaryXAA_eqeqXX': [(lambda g:g.inject(base64.b64decode('AA==')))], + 'g_valuesXblobX_isXtypeOfXGType_BINARYXX': [(lambda g:g.add_v('data').property('blob', base64.b64decode('AQID'))), (lambda g:g.V().values('blob').is_(P.type_of(GType.BINARY)))], + 'g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX': [(lambda g:g.inject(base64.b64decode('AQID')).is_(P.eq(base64.b64decode('AQID'))))], 'g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX': [(lambda g:g.add_v('data').property('int', 5)), (lambda g:g.V().values('int').as_number(GType.BYTE).is_(P.type_of(GType.BYTE)))], 'g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_mathXaddX20XX': [(lambda g:g.add_v('data').property('int', 10)), (lambda g:g.V().values('int').as_number(GType.BYTE).is_(P.type_of(GType.BYTE)).math('_ + 20'))], 'g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_isXltX10XX': [(lambda g:g.add_v('data').property('int', 7)), (lambda g:g.V().values('int').as_number(GType.BYTE).is_(P.type_of(GType.BYTE)).is_(P.lt(10)))], @@ -207,6 +212,12 @@ world.gremlins = { 'g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_chooseXisXeqX12XX_constantXtwelveX_constantXotherXX': [(lambda g:g.add_v('data').property('int', 12)), (lambda g:g.V().values('int').as_number(GType.BYTE).is_(P.type_of(GType.BYTE)).choose(__.is_(P.eq(12)), __.constant('twelve'), __.constant('other')))], 'g_injectX15X_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX_groupCount': [(lambda g:g.inject(15).as_number(GType.BYTE).is_(P.type_of(GType.BYTE)).group_count())], 'g_V_valuesXageX_isXtypeOfXGType_BYTEXX': [(lambda g:g.V().values('age').is_(P.type_of(GType.BYTE)))], + 'g_injectXaX': [(lambda g:g.inject(SingleChar('a')))], + 'g_injectXescaped_quoteX': [(lambda g:g.inject(SingleChar('\"')))], + 'g_injectXescaped_backslashX': [(lambda g:g.inject(SingleChar('\\')))], + 'g_injectXunicodeX': [(lambda g:g.inject(SingleChar('\u00E9')))], + 'g_valuesXinitialX_isXtypeOfXGType_CHARXX': [(lambda g:g.add_v('data').property('initial', SingleChar('a'))), (lambda g:g.V().values('initial').is_(P.type_of(GType.CHAR)))], + 'g_injectXaX_isXeqXaXX': [(lambda g:g.inject(SingleChar('a')).is_(P.eq(SingleChar('a'))))], 'g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX': [(lambda g:g.add_v('event').property('datetime', datetime.datetime.fromisoformat('2023-08-08T00:00+00:00'))), (lambda g:g.V().values('datetime').is_(P.type_of(GType.DATETIME)))], 'g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_project_byXidentityX_byXdateAddXDT_dayX1XX': [(lambda g:g.add_v('event').property('datetime', datetime.datetime.fromisoformat('2023-08-08T00:00+00:00'))), (lambda g:g.V().values('datetime').is_(P.type_of(GType.DATETIME)).project('original', 'nextDay').by(__.identity()).by(__.date_add(DT.day, 1)))], 'g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX_dateDiffXdatetimeX2023_08_10XX': [(lambda g:g.add_v('event').property('datetime', datetime.datetime.fromisoformat('2023-08-08T00:00+00:00'))), (lambda g:g.V().values('datetime').is_(P.type_of(GType.DATETIME)).date_diff(datetime.datetime.fromisoformat('2023-08-08T00:00:30+00:00')))], @@ -226,6 +237,17 @@ world.gremlins = { 'g_V_valuesXdoubleX_isXtypeOfXGType_DOUBLEXX_order_byXascX': [(lambda g:g.add_v('data').property('double', 3.2).add_v('data').property('double', 1.8).add_v('data').property('double', 2.5)), (lambda g:g.V().values('double').is_(P.type_of(GType.DOUBLE)).order().by(Order.asc))], 'g_injectX5_5dX_isXtypeOfXGType_DOUBLEXX_groupCount': [(lambda g:g.inject(5.5).is_(P.type_of(GType.DOUBLE)).group_count())], 'g_V_valuesXageX_isXtypeOfXGType_DOUBLEXX': [(lambda g:g.V().values('age').is_(P.type_of(GType.DOUBLE)))], + 'g_injectXDurationXPT2H30MXX': [(lambda g:g.inject(timedelta(seconds=9000)))], + 'g_injectXDurationXPT0SXX': [(lambda g:g.inject(timedelta(seconds=0)))], + 'g_injectXDurationXPTneg30SXX': [(lambda g:g.inject(timedelta(seconds=-30)))], + 'g_injectXDurationXnegPT30SXX': [(lambda g:g.inject(timedelta(seconds=-30)))], + 'g_injectXDurationXPT0_5SXX': [(lambda g:g.inject(timedelta(seconds=0,microseconds=500000)))], + 'g_injectXDurationXP1DT12HXX': [(lambda g:g.inject(timedelta(seconds=129600)))], + 'g_injectXDurationXP2DXX': [(lambda g:g.inject(timedelta(seconds=172800)))], + 'g_injectXDurationXPT1H30M15SXX': [(lambda g:g.inject(timedelta(seconds=5415)))], + 'g_injectXDurationXPTneg0_5SXX': [(lambda g:g.inject(timedelta(seconds=-1,microseconds=500000)))], + 'g_valuesXlengthX_isXtypeOfXGType_DURATIONXX': [(lambda g:g.add_v('data').property('length', timedelta(seconds=9000))), (lambda g:g.V().values('length').is_(P.type_of(GType.DURATION)))], + 'g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX': [(lambda g:g.inject(timedelta(seconds=9000)).is_(P.gt(timedelta(seconds=3600))))], 'g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX': [(lambda g:g.add_v('data').property('float', 2.5)), (lambda g:g.V().values('float').as_number(GType.FLOAT).is_(P.type_of(GType.FLOAT)))], 'g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_mathXmulX2XX': [(lambda g:g.add_v('data').property('float', 3.0)), (lambda g:g.V().values('float').as_number(GType.FLOAT).is_(P.type_of(GType.FLOAT)).math('_ * 2'))], 'g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX_isXeqX1_5XX': [(lambda g:g.add_v('data').property('float', 1.5)), (lambda g:g.V().values('float').as_number(GType.FLOAT).is_(P.type_of(GType.FLOAT)).is_(P.eq(1.5)))], diff --git a/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py b/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py index 40f0ecd33b..4f852d8e25 100644 --- a/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py +++ b/gremlin-python/src/main/python/tests/unit/process/test_gremlin_lang.py @@ -482,66 +482,19 @@ class TestGremlinLang(object): tests.append([g.add_v('test').property('date', datetime(2021, 2, 1, 9, 30, tzinfo=timezone(timedelta(hours=-5)))), "g.addV('test').property('date',datetime(\"2021-02-01T09:30:00-05:00\"))"]) + # Character - single quote (no feature equivalent) # 122 - tests.append([g.inject(SingleChar('a')), - "g.inject('a'c)"]) - - # 123 - double quote char: repr gives '"', so output is '"'c - tests.append([g.inject(SingleChar('"')), - "g.inject('\"'c)"]) - - # 124 - single quote char: repr gives "'" (double-quoted), so output is "'"c tests.append([g.inject(SingleChar("'")), "g.inject(\"'\"c)"]) - - # 125 - backslash char: repr gives '\\', so output is '\\'c - tests.append([g.inject(SingleChar('\\')), - "g.inject('\\\\'c)"]) - - # 126 - unicode char: repr gives 'é', so output is 'é'c - tests.append([g.inject(SingleChar('\u00e9')), - "g.inject('\u00e9'c)"]) - # 127 - tests.append([g.inject(timedelta(hours=2, minutes=30)), - 'g.inject(Duration("PT2H30M"))']) - - # 128 - tests.append([g.inject(timedelta(0)), - 'g.inject(Duration("PT0S"))']) - - # 129 - tests.append([g.inject(timedelta(seconds=-30)), - 'g.inject(Duration("PT-30S"))']) - - # 130 - tests.append([g.inject(timedelta(milliseconds=500)), - 'g.inject(Duration("PT0.5S"))']) - - # 131 + # Duration - Python-specific: microsecond precision, days+hours normalization + # 123 tests.append([g.inject(timedelta(microseconds=1)), 'g.inject(Duration("PT0.000001S"))']) - - # 132 - tests.append([g.inject(timedelta(milliseconds=-500)), - 'g.inject(Duration("PT-0.5S"))']) - - # 133 + # 124 tests.append([g.inject(timedelta(days=1, hours=12)), 'g.inject(Duration("PT36H"))']) - # 134 - tests.append([g.inject(bytes([1, 2, 3])), - 'g.inject(Binary("AQID"))']) - - # 135 - tests.append([g.inject(bytes()), - 'g.inject(Binary(""))']) - - # 136 - tests.append([g.inject(bytes([0])), - 'g.inject(Binary("AA=="))']) - for t in range(len(tests)): gremlin_lang = tests[t][0].gremlin_lang.get_gremlin() assert gremlin_lang == tests[t][1] 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 09987991ac..208db90d46 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 @@ -85,6 +85,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; +import java.nio.ByteBuffer; +import java.time.Duration; +import java.util.Base64; import java.util.UUID; import static org.apache.commons.text.StringEscapeUtils.escapeJava; @@ -165,6 +168,10 @@ public final class StepDefinition { add(Pair.with(Pattern.compile("dt\\[(.*)\\]"), s -> String.format("datetime('%s')", s))); + add(Pair.with(Pattern.compile("char\\[(.)\\]"), s -> String.format("\"%s\"c", s))); + add(Pair.with(Pattern.compile("dur\\[(.*)\\]"), s -> String.format("Duration(\"%s\")", s))); + add(Pair.with(Pattern.compile("bin\\[(.*)\\]"), s -> String.format("Binary(\"%s\")", s))); + add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> world.convertIdToScript(g.V().has("name", s).id().next(), Vertex.class))); add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> world.convertIdToScript(g.V().has("name", s).id().next(), Vertex.class))); add(Pair.with(Pattern.compile("e\\[(.+)\\]\\.id"), s -> world.convertIdToScript(getEdgeId(g, s), Edge.class))); @@ -237,6 +244,10 @@ public final class StepDefinition { add(Pair.with(Pattern.compile("dt\\[(.*)\\]"), DatetimeHelper::parse)); add(Pair.with(Pattern.compile("uuid\\[(.*)\\]"), UUID::fromString)); + add(Pair.with(Pattern.compile("char\\[(.)\\]"), s -> s.charAt(0))); + add(Pair.with(Pattern.compile("dur\\[(.*)\\]"), s -> Duration.parse(s))); + add(Pair.with(Pattern.compile("bin\\[(.*)\\]"), s -> ByteBuffer.wrap(Base64.getDecoder().decode(s)))); + add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.id"), s -> g.V().has("name", s).id().next())); add(Pair.with(Pattern.compile("v\\[(.+)\\]\\.sid"), s -> g.V().has("name", s).id().next().toString())); add(Pair.with(Pattern.compile("v\\[(.+)\\]"), s -> detachVertex(g.V().has("name", s).next()))); diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json index 35df2cff6a..ce4a3b7cd1 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json @@ -3010,6 +3010,103 @@ } ] }, + { + "scenario": "g_injectXBinaryXAQIDXX", + "traversals": [ + { + "original": "g.inject(Binary(\"AQID\"))", + "language": "g.inject(Binary(\"AQID\"))", + "canonical": "g.inject(Binary(\"AQID\"))", + "anonymized": "g.inject(bytebuffer0)", + "dotnet": "g.Inject<object>(Convert.FromBase64String(\"AQID\"))", + "go": "g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}})", + "groovy": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "java": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "javascript": "g.inject(Buffer.from(\"AQID\",'base64'))", + "python": "g.inject(base64.b64decode('AQID'))" + } + ] + }, + { + "scenario": "g_injectXBinaryXemptyXX", + "traversals": [ + { + "original": "g.inject(Binary(\"\"))", + "language": "g.inject(Binary(\"\"))", + "canonical": "g.inject(Binary(\"\"))", + "anonymized": "g.inject(bytebuffer0)", + "dotnet": "g.Inject<object>(Convert.FromBase64String(\"\"))", + "go": "g.Inject(gremlingo.ByteBuffer{Data: []byte{}})", + "groovy": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"\")))", + "java": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"\")))", + "javascript": "g.inject(Buffer.from(\"\",'base64'))", + "python": "g.inject(base64.b64decode(''))" + } + ] + }, + { + "scenario": "g_injectXBinaryXAA_eqeqXX", + "traversals": [ + { + "original": "g.inject(Binary(\"AA==\"))", + "language": "g.inject(Binary(\"AA==\"))", + "canonical": "g.inject(Binary(\"AA==\"))", + "anonymized": "g.inject(bytebuffer0)", + "dotnet": "g.Inject<object>(Convert.FromBase64String(\"AA==\"))", + "go": "g.Inject(gremlingo.ByteBuffer{Data: []byte{0}})", + "groovy": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AA==\")))", + "java": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AA==\")))", + "javascript": "g.inject(Buffer.from(\"AA==\",'base64'))", + "python": "g.inject(base64.b64decode('AA=='))" + } + ] + }, + { + "scenario": "g_valuesXblobX_isXtypeOfXGType_BINARYXX", + "traversals": [ + { + "original": "g.addV(\"data\").property(\"blob\", Binary(\"AQID\"))", + "language": "g.addV(\"data\").property(\"blob\", Binary(\"AQID\"))", + "canonical": "g.addV(\"data\").property(\"blob\", Binary(\"AQID\"))", + "anonymized": "g.addV(string0).property(string1, bytebuffer0)", + "dotnet": "g.AddV((string) \"data\").Property(\"blob\", Convert.FromBase64String(\"AQID\"))", + "go": "g.AddV(\"data\").Property(\"blob\", gremlingo.ByteBuffer{Data: []byte{1,2,3}})", + "groovy": "g.addV(\"data\").property(\"blob\", ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "java": "g.addV(\"data\").property(\"blob\", ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\")))", + "javascript": "g.addV(\"data\").property(\"blob\", Buffer.from(\"AQID\",'base64'))", + "python": "g.add_v('data').property('blob', base64.b64decode('AQID'))" + }, + { + "original": "g.V().values(\"blob\").is(P.typeOf(GType.BINARY))", + "language": "g.V().values(\"blob\").is(P.typeOf(GType.BINARY))", + "canonical": "g.V().values(\"blob\").is(P.typeOf(GType.BINARY))", + "anonymized": "g.V().values(string0).is(P.typeOf(GType.BINARY))", + "dotnet": "g.V().Values<object>(\"blob\").Is(P.TypeOf(GType.Binary))", + "go": "g.V().Values(\"blob\").Is(gremlingo.P.TypeOf(gremlingo.GType.Binary))", + "groovy": "g.V().values(\"blob\").is(P.typeOf(GType.BINARY))", + "java": "g.V().values(\"blob\").is(P.typeOf(GType.BINARY))", + "javascript": "g.V().values(\"blob\").is(P.typeOf(GType.binary))", + "python": "g.V().values('blob').is_(P.type_of(GType.BINARY))" + } + ] + }, + { + "scenario": "g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX", + "traversals": [ + { + "original": "g.inject(Binary(\"AQID\")).is(P.eq(Binary(\"AQID\")))", + "language": "g.inject(Binary(\"AQID\")).is(P.eq(Binary(\"AQID\")))", + "canonical": "g.inject(Binary(\"AQID\")).is(P.eq(Binary(\"AQID\")))", + "anonymized": "g.inject(bytebuffer0).is(P.eq(bytebuffer0))", + "dotnet": "g.Inject<object>(Convert.FromBase64String(\"AQID\")).Is(P.Eq(Convert.FromBase64String(\"AQID\")))", + "go": "g.Inject(gremlingo.ByteBuffer{Data: []byte{1,2,3}}).Is(gremlingo.P.Eq(gremlingo.ByteBuffer{Data: []byte{1,2,3}}))", + "groovy": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\"))).is(P.eq(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\"))))", + "java": "g.inject(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\"))).is(P.eq(ByteBuffer.wrap(Base64.getDecoder().decode(\"AQID\"))))", + "javascript": "g.inject(Buffer.from(\"AQID\",'base64')).is(P.eq(Buffer.from(\"AQID\",'base64')))", + "python": "g.inject(base64.b64decode('AQID')).is_(P.eq(base64.b64decode('AQID')))" + } + ] + }, { "scenario": "g_V_valuesXintX_asNumberXGType_BYTEX_isXtypeOfXGType_BYTEXX", "traversals": [ @@ -3218,6 +3315,108 @@ } ] }, + { + "scenario": "g_injectXaX", + "traversals": [ + { + "original": "g.inject(\"a\"c)", + "language": "g.inject(\"a\"c)", + "canonical": "g.inject(\"a\"c)", + "anonymized": "g.inject(character0)", + "dotnet": "g.Inject<object>('a')", + "groovy": "g.inject('a' as char)", + "java": "g.inject('a')", + "python": "g.inject(SingleChar('a'))" + } + ] + }, + { + "scenario": "g_injectXescaped_quoteX", + "traversals": [ + { + "original": "g.inject(\"\\\"\"c)", + "language": "g.inject(\"\\\"\"c)", + "canonical": "g.inject(\"\\\"\"c)", + "anonymized": "g.inject(character0)", + "dotnet": "g.Inject<object>('\\\"')", + "groovy": "g.inject('\\\"' as char)", + "java": "g.inject('\\\"')", + "python": "g.inject(SingleChar('\\\"'))" + } + ] + }, + { + "scenario": "g_injectXescaped_backslashX", + "traversals": [ + { + "original": "g.inject(\"\\\\\"c)", + "language": "g.inject(\"\\\\\"c)", + "canonical": "g.inject(\"\\\\\"c)", + "anonymized": "g.inject(character0)", + "dotnet": "g.Inject<object>('\\\\')", + "groovy": "g.inject('\\\\' as char)", + "java": "g.inject('\\\\')", + "python": "g.inject(SingleChar('\\\\'))" + } + ] + }, + { + "scenario": "g_injectXunicodeX", + "traversals": [ + { + "original": "g.inject(\"\\u00E9\"c)", + "language": "g.inject(\"\\u00E9\"c)", + "canonical": "g.inject(\"\\u00E9\"c)", + "anonymized": "g.inject(character0)", + "dotnet": "g.Inject<object>('\\u00E9')", + "groovy": "g.inject('\\u00E9' as char)", + "java": "g.inject('\\u00E9')", + "python": "g.inject(SingleChar('\\u00E9'))" + } + ] + }, + { + "scenario": "g_valuesXinitialX_isXtypeOfXGType_CHARXX", + "traversals": [ + { + "original": "g.addV(\"data\").property(\"initial\", \"a\"c)", + "language": "g.addV(\"data\").property(\"initial\", \"a\"c)", + "canonical": "g.addV(\"data\").property(\"initial\", \"a\"c)", + "anonymized": "g.addV(string0).property(string1, character0)", + "dotnet": "g.AddV((string) \"data\").Property(\"initial\", 'a')", + "groovy": "g.addV(\"data\").property(\"initial\", 'a' as char)", + "java": "g.addV(\"data\").property(\"initial\", 'a')", + "python": "g.add_v('data').property('initial', SingleChar('a'))" + }, + { + "original": "g.V().values(\"initial\").is(P.typeOf(GType.CHAR))", + "language": "g.V().values(\"initial\").is(P.typeOf(GType.CHAR))", + "canonical": "g.V().values(\"initial\").is(P.typeOf(GType.CHAR))", + "anonymized": "g.V().values(string0).is(P.typeOf(GType.CHAR))", + "dotnet": "g.V().Values<object>(\"initial\").Is(P.TypeOf(GType.Char))", + "go": "g.V().Values(\"initial\").Is(gremlingo.P.TypeOf(gremlingo.GType.Char))", + "groovy": "g.V().values(\"initial\").is(P.typeOf(GType.CHAR))", + "java": "g.V().values(\"initial\").is(P.typeOf(GType.CHAR))", + "javascript": "g.V().values(\"initial\").is(P.typeOf(GType.char))", + "python": "g.V().values('initial').is_(P.type_of(GType.CHAR))" + } + ] + }, + { + "scenario": "g_injectXaX_isXeqXaXX", + "traversals": [ + { + "original": "g.inject(\"a\"c).is(P.eq(\"a\"c))", + "language": "g.inject(\"a\"c).is(P.eq(\"a\"c))", + "canonical": "g.inject(\"a\"c).is(P.eq(\"a\"c))", + "anonymized": "g.inject(character0).is(P.eq(character0))", + "dotnet": "g.Inject<object>('a').Is(P.Eq('a'))", + "groovy": "g.inject('a' as char).is(P.eq('a' as char))", + "java": "g.inject('a').is(P.eq('a'))", + "python": "g.inject(SingleChar('a')).is_(P.eq(SingleChar('a')))" + } + ] + }, { "scenario": "g_V_valuesXdatetimeX_isXtypeOfXGType_DATETIMEXX", "traversals": [ @@ -3709,6 +3908,194 @@ } ] }, + { + "scenario": "g_injectXDurationXPT2H30MXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT2H30M\"))", + "language": "g.inject(Duration(\"PT2H30M\"))", + "canonical": "g.inject(Duration(\"PT2H30M\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT2H30M\"))", + "go": "g.Inject(time.Duration(9000000000000))", + "groovy": "g.inject(Duration.parse(\"PT2H30M\"))", + "java": "g.inject(Duration.parse(\"PT2H30M\"))", + "python": "g.inject(timedelta(seconds=9000))" + } + ] + }, + { + "scenario": "g_injectXDurationXPT0SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT0S\"))", + "language": "g.inject(Duration(\"PT0S\"))", + "canonical": "g.inject(Duration(\"PT0S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT0S\"))", + "go": "g.Inject(time.Duration(0))", + "groovy": "g.inject(Duration.parse(\"PT0S\"))", + "java": "g.inject(Duration.parse(\"PT0S\"))", + "python": "g.inject(timedelta(seconds=0))" + } + ] + }, + { + "scenario": "g_injectXDurationXPTneg30SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT-30S\"))", + "language": "g.inject(Duration(\"PT-30S\"))", + "canonical": "g.inject(Duration(\"PT-30S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT-30S\"))", + "go": "g.Inject(time.Duration(-30000000000))", + "groovy": "g.inject(Duration.parse(\"PT-30S\"))", + "java": "g.inject(Duration.parse(\"PT-30S\"))", + "python": "g.inject(timedelta(seconds=-30))" + } + ] + }, + { + "scenario": "g_injectXDurationXnegPT30SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"-PT30S\"))", + "language": "g.inject(Duration(\"-PT30S\"))", + "canonical": "g.inject(Duration(\"-PT30S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"-PT30S\"))", + "go": "g.Inject(time.Duration(-30000000000))", + "groovy": "g.inject(Duration.parse(\"-PT30S\"))", + "java": "g.inject(Duration.parse(\"-PT30S\"))", + "python": "g.inject(timedelta(seconds=-30))" + } + ] + }, + { + "scenario": "g_injectXDurationXPT0_5SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT0.5S\"))", + "language": "g.inject(Duration(\"PT0.5S\"))", + "canonical": "g.inject(Duration(\"PT0.5S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT0.5S\"))", + "go": "g.Inject(time.Duration(500000000))", + "groovy": "g.inject(Duration.parse(\"PT0.5S\"))", + "java": "g.inject(Duration.parse(\"PT0.5S\"))", + "python": "g.inject(timedelta(seconds=0,microseconds=500000))" + } + ] + }, + { + "scenario": "g_injectXDurationXP1DT12HXX", + "traversals": [ + { + "original": "g.inject(Duration(\"P1DT12H\"))", + "language": "g.inject(Duration(\"P1DT12H\"))", + "canonical": "g.inject(Duration(\"P1DT12H\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"P1DT12H\"))", + "go": "g.Inject(time.Duration(129600000000000))", + "groovy": "g.inject(Duration.parse(\"P1DT12H\"))", + "java": "g.inject(Duration.parse(\"P1DT12H\"))", + "python": "g.inject(timedelta(seconds=129600))" + } + ] + }, + { + "scenario": "g_injectXDurationXP2DXX", + "traversals": [ + { + "original": "g.inject(Duration(\"P2D\"))", + "language": "g.inject(Duration(\"P2D\"))", + "canonical": "g.inject(Duration(\"P2D\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"P2D\"))", + "go": "g.Inject(time.Duration(172800000000000))", + "groovy": "g.inject(Duration.parse(\"P2D\"))", + "java": "g.inject(Duration.parse(\"P2D\"))", + "python": "g.inject(timedelta(seconds=172800))" + } + ] + }, + { + "scenario": "g_injectXDurationXPT1H30M15SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT1H30M15S\"))", + "language": "g.inject(Duration(\"PT1H30M15S\"))", + "canonical": "g.inject(Duration(\"PT1H30M15S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT1H30M15S\"))", + "go": "g.Inject(time.Duration(5415000000000))", + "groovy": "g.inject(Duration.parse(\"PT1H30M15S\"))", + "java": "g.inject(Duration.parse(\"PT1H30M15S\"))", + "python": "g.inject(timedelta(seconds=5415))" + } + ] + }, + { + "scenario": "g_injectXDurationXPTneg0_5SXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT-0.5S\"))", + "language": "g.inject(Duration(\"PT-0.5S\"))", + "canonical": "g.inject(Duration(\"PT-0.5S\"))", + "anonymized": "g.inject(duration0)", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT-0.5S\"))", + "go": "g.Inject(time.Duration(-500000000))", + "groovy": "g.inject(Duration.parse(\"PT-0.5S\"))", + "java": "g.inject(Duration.parse(\"PT-0.5S\"))", + "python": "g.inject(timedelta(seconds=-1,microseconds=500000))" + } + ] + }, + { + "scenario": "g_valuesXlengthX_isXtypeOfXGType_DURATIONXX", + "traversals": [ + { + "original": "g.addV(\"data\").property(\"length\", Duration(\"PT2H30M\"))", + "language": "g.addV(\"data\").property(\"length\", Duration(\"PT2H30M\"))", + "canonical": "g.addV(\"data\").property(\"length\", Duration(\"PT2H30M\"))", + "anonymized": "g.addV(string0).property(string1, duration0)", + "dotnet": "g.AddV((string) \"data\").Property(\"length\", XmlConvert.ToTimeSpan(\"PT2H30M\"))", + "go": "g.AddV(\"data\").Property(\"length\", time.Duration(9000000000000))", + "groovy": "g.addV(\"data\").property(\"length\", Duration.parse(\"PT2H30M\"))", + "java": "g.addV(\"data\").property(\"length\", Duration.parse(\"PT2H30M\"))", + "python": "g.add_v('data').property('length', timedelta(seconds=9000))" + }, + { + "original": "g.V().values(\"length\").is(P.typeOf(GType.DURATION))", + "language": "g.V().values(\"length\").is(P.typeOf(GType.DURATION))", + "canonical": "g.V().values(\"length\").is(P.typeOf(GType.DURATION))", + "anonymized": "g.V().values(string0).is(P.typeOf(GType.DURATION))", + "dotnet": "g.V().Values<object>(\"length\").Is(P.TypeOf(GType.Duration))", + "go": "g.V().Values(\"length\").Is(gremlingo.P.TypeOf(gremlingo.GType.Duration))", + "groovy": "g.V().values(\"length\").is(P.typeOf(GType.DURATION))", + "java": "g.V().values(\"length\").is(P.typeOf(GType.DURATION))", + "javascript": "g.V().values(\"length\").is(P.typeOf(GType.duration))", + "python": "g.V().values('length').is_(P.type_of(GType.DURATION))" + } + ] + }, + { + "scenario": "g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX", + "traversals": [ + { + "original": "g.inject(Duration(\"PT2H30M\")).is(P.gt(Duration(\"PT1H\")))", + "language": "g.inject(Duration(\"PT2H30M\")).is(P.gt(Duration(\"PT1H\")))", + "canonical": "g.inject(Duration(\"PT2H30M\")).is(P.gt(Duration(\"PT1H\")))", + "anonymized": "g.inject(duration0).is(P.gt(duration1))", + "dotnet": "g.Inject<object>(XmlConvert.ToTimeSpan(\"PT2H30M\")).Is(P.Gt(XmlConvert.ToTimeSpan(\"PT1H\")))", + "go": "g.Inject(time.Duration(9000000000000)).Is(gremlingo.P.Gt(time.Duration(3600000000000)))", + "groovy": "g.inject(Duration.parse(\"PT2H30M\")).is(P.gt(Duration.parse(\"PT1H\")))", + "java": "g.inject(Duration.parse(\"PT2H30M\")).is(P.gt(Duration.parse(\"PT1H\")))", + "python": "g.inject(timedelta(seconds=9000)).is_(P.gt(timedelta(seconds=3600)))" + } + ] + }, { "scenario": "g_V_valuesXfloatX_isXtypeOfXGType_FLOATXX", "traversals": [ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Binary.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Binary.feature new file mode 100644 index 0000000000..c8f365c268 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Binary.feature @@ -0,0 +1,82 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +@StepClassData @DataBinary +Feature: Data - BINARY + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXBinaryXAQIDXX + Given the empty graph + And the traversal of + """ + g.inject(Binary("AQID")) + """ + When iterated to list + Then the result should be unordered + | result | + | bin[AQID] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXBinaryXemptyXX + Given the empty graph + And the traversal of + """ + g.inject(Binary("")) + """ + When iterated to list + Then the result should be unordered + | result | + | bin[] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXBinaryXAA_eqeqXX + Given the empty graph + And the traversal of + """ + g.inject(Binary("AA==")) + """ + When iterated to list + Then the result should be unordered + | result | + | bin[AA==] | + + Scenario: g_valuesXblobX_isXtypeOfXGType_BINARYXX + Given the empty graph + And the graph initializer of + """ + g.addV("data").property("blob", Binary("AQID")) + """ + And the traversal of + """ + g.V().values("blob").is(P.typeOf(GType.BINARY)) + """ + When iterated to list + Then the result should be unordered + | result | + | bin[AQID] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXBinaryXAQIDXX_isXeqXBinaryXAQIDXXX + Given the empty graph + And the traversal of + """ + g.inject(Binary("AQID")).is(P.eq(Binary("AQID"))) + """ + When iterated to list + Then the result should be unordered + | result | + | bin[AQID] | diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Char.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Char.feature new file mode 100644 index 0000000000..321bbe6421 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Char.feature @@ -0,0 +1,94 @@ +# 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. + +@StepClassData @DataChar +Feature: Data - CHAR + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXaX + Given the empty graph + And the traversal of + """ + g.inject("a"c) + """ + When iterated to list + Then the result should be unordered + | result | + | char[a] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXescaped_quoteX + Given the empty graph + And the traversal of + """ + g.inject("\""c) + """ + When iterated to list + Then the result should be unordered + | result | + | char["] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXescaped_backslashX + Given the empty graph + And the traversal of + """ + g.inject("\\"c) + """ + When iterated to list + Then the result should be unordered + | result | + | char[\] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXunicodeX + Given the empty graph + And the traversal of + """ + g.inject("\u00E9"c) + """ + When iterated to list + Then the result should be unordered + | result | + | char[é] | + + Scenario: g_valuesXinitialX_isXtypeOfXGType_CHARXX + Given the empty graph + And the graph initializer of + """ + g.addV("data").property("initial", "a"c) + """ + And the traversal of + """ + g.V().values("initial").is(P.typeOf(GType.CHAR)) + """ + When iterated to list + Then the result should be unordered + | result | + | char[a] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXaX_isXeqXaXX + Given the empty graph + And the traversal of + """ + g.inject("a"c).is(P.eq("a"c)) + """ + When iterated to list + Then the result should be unordered + | result | + | char[a] | diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Duration.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Duration.feature new file mode 100644 index 0000000000..c9d0a73686 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/data/Duration.feature @@ -0,0 +1,154 @@ +# 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. + +@StepClassData @DataDuration +Feature: Data - DURATION + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPT2H30MXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT2H30M")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT2H30M] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPT0SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT0S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT0S] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPTneg30SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT-30S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT-30S] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXnegPT30SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("-PT30S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT-30S] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPT0_5SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT0.5S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT0.5S] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXP1DT12HXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("P1DT12H")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT36H] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXP2DXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("P2D")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT48H] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPT1H30M15SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT1H30M15S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT1H30M15S] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPTneg0_5SXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT-0.5S")) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT-0.5S] | + + Scenario: g_valuesXlengthX_isXtypeOfXGType_DURATIONXX + Given the empty graph + And the graph initializer of + """ + g.addV("data").property("length", Duration("PT2H30M")) + """ + And the traversal of + """ + g.V().values("length").is(P.typeOf(GType.DURATION)) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT2H30M] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXDurationXPT2H30MXX_isXgtXDurationXPT1HXXX + Given the empty graph + And the traversal of + """ + g.inject(Duration("PT2H30M")).is(P.gt(Duration("PT1H"))) + """ + When iterated to list + Then the result should be unordered + | result | + | dur[PT2H30M] |
