This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch TINKERPOP-2992 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit abed92a518306dbd4690ecce91c9557410621567 Author: Stephen Mallette <[email protected]> AuthorDate: Fri Feb 6 08:32:11 2026 -0500 TINKERPOP-2992 Fixed orderability bug on OffsetDateTime Fixed javascript translator for uuid. Added testing on date, set, uuid. --- .gitignore | 1 + CHANGELOG.asciidoc | 2 + .../translator/JavascriptTranslateVisitor.java | 4 + .../traversal/translator/JavascriptTranslator.java | 4 +- .../gremlin/util/GremlinValueComparator.java | 5 +- .../language/translator/GremlinTranslatorTest.java | 2 +- .../gremlin/process/traversal/OrderTest.java | 28 +- .../translator/JavascriptTranslatorTest.java | 2 +- .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 4 +- gremlin-go/driver/cucumber/cucumberSteps_test.go | 373 ++++++++++----------- gremlin-go/driver/cucumber/gremlin.go | 4 +- .../test/cucumber/feature-steps.js | 7 +- .../gremlin-javascript/test/cucumber/gremlin.js | 22 +- gremlin-python/src/main/python/radish/gremlin.py | 4 +- .../process/traversal/step/OrderabilityTest.java | 10 +- .../test/features/semantics/Orderability.feature | 17 +- pom.xml | 1 + 17 files changed, 261 insertions(+), 229 deletions(-) diff --git a/.gitignore b/.gitignore index d27fcdbbb8..a9f890ce33 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ ext/ __pycache__/ *.py[cdo] **/venv/ +**/.venv/ __version__.py .glv settings.xml diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 22f3c2218f..7335f5d4f1 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -25,6 +25,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima This release also includes changes from <<release-3-7-6, 3.7.6>>. +* Fixed bug in Gremlin orderability semantics for `OffsetDateTime`. +* Fixed bug in Javascript translation for UUID. * Fixed bug in pre-repeat() `emit()/until()` where `emit()` and `until()` traversers weren't added to the results. * Expose serialization functions for alternative transport protocols in gremlin-go * Improved Gremlint formatting to keep the first argument for a step on the same line if line breaks were required to meet max line length. 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 ac3ccb42db..8971391c2d 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 @@ -38,6 +38,8 @@ import java.util.stream.Collectors; * <li>Makes anonymous traversals explicit with double underscore</li> * <li>Makes enums explicit with their proper name</li> * </ul> + * <p/> + * Assumes use of https://www.npmjs.com/package/uuid library for UUID handling. */ public class JavascriptTranslateVisitor extends AbstractTranslateVisitor { public JavascriptTranslateVisitor() { @@ -222,7 +224,9 @@ public class JavascriptTranslateVisitor extends AbstractTranslateVisitor { sb.append("uuid.v4()"); return null; } + sb.append("uuid.parse("); visitStringLiteral(ctx.stringLiteral()); + sb.append(")"); return null; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java index 643b8f44f4..2a7c22266d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslator.java @@ -58,6 +58,8 @@ import java.util.function.UnaryOperator; /** * Converts bytecode to a Javascript string of Gremlin. + * <p/> + * Assumes use of https://www.npmjs.com/package/uuid library for UUID handling. * * @author Stephen Mallette (http://stephen.genoprime.com) */ @@ -157,7 +159,7 @@ public final class JavascriptTranslator implements Translator.ScriptTranslator { @Override protected String getSyntax(final UUID o) { - return "'" + o.toString() + "'"; + return "uuid.parse('" + o.toString() + "')"; } @Override diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java index 2f6e374d24..be1b21b6f3 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java @@ -26,12 +26,11 @@ import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; +import java.time.OffsetDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.EnumMap; import java.util.Iterator; import java.util.List; @@ -241,7 +240,7 @@ public abstract class GremlinValueComparator implements Comparator<Object> { Nulltype, Boolean (Boolean.class), Number (Number.class), - Date (Date.class), + Date (OffsetDateTime.class), String (String.class), UUID (UUID.class), Vertex (Vertex.class), 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 f1859c7a97..27c6ef37dc 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 @@ -159,7 +159,7 @@ public class GremlinTranslatorTest { "g.Inject(uuid.MustParse(\"f47af10b-58cc-4372-a567-0f02b2f3d479\"))", null, null, - "g.inject(\"f47af10b-58cc-4372-a567-0f02b2f3d479\")", + "g.inject(uuid.parse(\"f47af10b-58cc-4372-a567-0f02b2f3d479\"))", "g.inject(uuid.UUID('f47af10b-58cc-4372-a567-0f02b2f3d479'))"}, {"g.inject(UUID())", null, diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java index 9d17501923..872ea6b404 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/OrderTest.java @@ -29,12 +29,14 @@ import org.junit.runners.Parameterized; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; +import java.time.OffsetDateTime; import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asSet; import static org.apache.tinkerpop.gremlin.util.CollectionUtil.asList; @@ -52,8 +54,6 @@ public class OrderTest { @RunWith(Parameterized.class) public static class OrderListTest { - private static final SimpleDateFormat formatter = new SimpleDateFormat("dd-MMM-yyyy", Locale.US); - @Parameterized.Parameters(name = "{0}.sort({1}) = {2}") public static Iterable<Object[]> data() throws ParseException { return new ArrayList<>(Arrays.asList(new Object[][]{ @@ -61,10 +61,26 @@ public class OrderTest { {Order.asc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("a", "b", "c", "d")}, {Order.desc, Arrays.asList("b", "a", "c", "d"), Arrays.asList("d", "c", "b", "a")}, {Order.desc, Arrays.asList("c", "a", null, "d"), Arrays.asList("d", "c", "a", null)}, - {Order.asc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")), - Arrays.asList(formatter.parse("1-Jan-2008"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"))}, - {Order.desc, Arrays.asList(formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2008")), - Arrays.asList(formatter.parse("1-Jan-2020"), formatter.parse("1-Jan-2018"), formatter.parse("1-Jan-2008"))}, + {Order.asc, Arrays.asList( + OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2008, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) + ), + Arrays.asList( + OffsetDateTime.of(2008, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) + )}, + {Order.desc, Arrays.asList( + OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2020, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC), + OffsetDateTime.of(2008, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC) + ), + Arrays.asList( + OffsetDateTime.of(2020, 1, 1, 0, 0, 0, 0, java.time.ZoneOffset.UTC), + OffsetDateTime.of(2018, 1, 1, 0, 0, 0, 0, java.time.ZoneOffset.UTC), + OffsetDateTime.of(2008, 1, 1, 0, 0, 0, 0, java.time.ZoneOffset.UTC) + )}, {Order.desc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L, null)}, {Order.desc, Arrays.asList(100L, 1L, -1L, 0L), Arrays.asList(100L, 1L, 0L, -1L)}, {Order.asc, Arrays.asList(100L, 1L, null, -1L, 0L), Arrays.asList(null, -1L, 0L, 1L, 100L)}, diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java index 22c2e24636..cd87535eee 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java @@ -88,7 +88,7 @@ public class JavascriptTranslatorTest { @Test public void shouldTranslateUuid() { final UUID uuid = UUID.fromString("ffffffff-fd49-1e4b-0000-00000d4b8a1d"); - assertTranslation(String.format("'%s'", uuid), uuid); + assertTranslation(String.format("uuid.parse('%s')", uuid), uuid); } @Test diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 8cc90ef064..e4e1d04670 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -1888,8 +1888,8 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_V_properties_order_id", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Properties<object>().Order().Id()}}, {"g_E_properties_order_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name", "alice").As("a").AddE("self").From("a").To("a").Property("weight", 0.5d).Property("a", 10).AddE("self").From("a").To("a").Property("weight", 1.0d).Property("a", 11).AddE("self").From("a").To("a").Property("weight", 0.4d).Property("a", 12).AddE("self").From("a").To("a").Property("weight", 1.0d).Property("a", 13).AddE("self") [...] {"g_E_properties_order_byXdescX_value", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name", "alice").As("a").AddE("self").From("a").To("a").Property("weight", 0.5d).Property("a", 10).AddE("self").From("a").To("a").Property("weight", 1.0d).Property("a", 11).AddE("self").From("a").To("a").Property("weight", 0.4d).Property("a", 12).AddE("self").From("a").To("a").Property("weight", 1.0d).Property("a", 13).Add [...] - {"g_inject_order", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("zzz", "foo", new List<object> { "a", "b", "c", "d" }, 1, new List<object> { "a", "b", "c" }, new Dictionary<object, object> {{ "a", "a" }, { "b", "b" }}, null, 2.0d, new Dictionary<object, object> {{ "a", "a" }, { "b", false }, { "c", "c" }}, "bar", true, false, Double.PositiveInfinity, Double.NaN, Double.NegativeInfinity).Order()}}, - {"g_inject_order_byXdescX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("zzz", "foo", new List<object> { "a", "b", "c", "d" }, 1, new List<object> { "a", "b", "c" }, new Dictionary<object, object> {{ "a", "a" }, { "b", "b" }}, null, 2.0d, new Dictionary<object, object> {{ "a", "a" }, { "b", false }, { "c", "c" }}, "bar", true, false, Double.PositiveInfinity, Double.NaN, Double.NegativeInfinity).Order().By(Order.Desc)}}, + {"g_inject_order", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("zzz", "foo", Guid.Parse("6100808b-62f9-42b7-957e-ed66(IComparator) p["c30"]f40d1"), new List<object> { "a", "b", "c", "d" }, 1, DateTimeOffset.Parse("2023-08-01T00:00Z"), new List<object> { "a", "b", "c" }, new Dictionary<object, object> {{ "a", "a" }, { "b", "b" }}, null, 2.0d, DateTimeOffset.Parse("2023-01-01T00:00Z"), new HashSet<object> { "x", "y" [...] + {"g_inject_order_byXdescX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>("zzz", "foo", Guid.Parse("6100808b-62f9-42b7-957e-ed66(IComparator) p["c30"]f40d1"), new List<object> { "a", "b", "c", "d" }, 1, DateTimeOffset.Parse("2023-08-01T00:00Z"), new List<object> { "a", "b", "c" }, new Dictionary<object, object> {{ "a", "a" }, { "b", "b" }}, null, 2.0d, DateTimeOffset.Parse("2023-01-01T00:00Z"), new HashSet<object> { [...] {"g_V_out_out_order_byXascX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Order().By(Order.Asc)}}, {"g_V_out_out_order_byXdescX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Order().By(Order.Desc)}}, {"g_V_out_out_asXheadX_path_order_byXascX_selectXheadX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().As("head").Path().Order().By(Order.Asc).Select<object>("head")}}, diff --git a/gremlin-go/driver/cucumber/cucumberSteps_test.go b/gremlin-go/driver/cucumber/cucumberSteps_test.go index 01e8271df5..eb8d71aeca 100644 --- a/gremlin-go/driver/cucumber/cucumberSteps_test.go +++ b/gremlin-go/driver/cucumber/cucumberSteps_test.go @@ -559,17 +559,21 @@ func (tg *tinkerPopGraph) theResultShouldBe(characterizedAs string, table *godog if ordered { if expectSet { for i, a := range actualResult { - if fmt.Sprint(a.(*gremlingo.SimpleSet).ToSlice()) != fmt.Sprint(expectedResult[i].(*gremlingo.SimpleSet).ToSlice()) { + actualSlice := a.(*gremlingo.SimpleSet).ToSlice() + expectedSlice := expectedResult[i].(*gremlingo.SimpleSet).ToSlice() + if !compareListEqualsWithOrder(expectedSlice, actualSlice) { return fmt.Errorf("actual result does not match expected (order expected)\nActual: %v\nExpected: %v", actualResult, expectedResult) } } } else if len(actualResult) == 1 && len(expectedResult) == 1 && reflect.TypeOf(actualResult[0]).Kind() == reflect.Map && reflect.TypeOf(expectedResult[0]).Kind() == reflect.Map { - if !compareMapEquals(actualResult[0].(map[interface{}]interface{}), expectedResult[0].(map[interface{}]interface{})) { + if !compareMapEquals(expectedResult[0].(map[interface{}]interface{}), actualResult[0].(map[interface{}]interface{})) { + return fmt.Errorf("actual result does not match expected (order expected)\nActual: %v\nExpected: %v", actualResult, expectedResult) + } + } else { + if !compareListEqualsWithOrder(expectedResult, actualResult) { return fmt.Errorf("actual result does not match expected (order expected)\nActual: %v\nExpected: %v", actualResult, expectedResult) } - } else if fmt.Sprint(actualResult) != fmt.Sprint(expectedResult) { - return fmt.Errorf("actual result does not match expected (order expected)\nActual: %v\nExpected: %v", actualResult, expectedResult) } } else { if characterizedAs == "of" { @@ -588,40 +592,20 @@ func (tg *tinkerPopGraph) theResultShouldBe(characterizedAs string, table *godog } } +// compareMapEquals compares two maps for equality by checking if they have the same length +// and if every key-value pair in the actual map has a logically equal counterpart in the expected map. func compareMapEquals(expected map[interface{}]interface{}, actual map[interface{}]interface{}) bool { + if len(actual) != len(expected) { + return false + } for k, a := range actual { var e interface{} containsKey := false for ke, ee := range expected { - if fmt.Sprint(k) == fmt.Sprint(ke) { + if compareSingleEqual(k, ke) { containsKey = true e = ee break - } else { - if reflect.ValueOf(k).Kind() == reflect.Ptr && - reflect.ValueOf(ke).Kind() == reflect.Ptr { - switch k.(type) { - case *gremlingo.Vertex: - switch ke.(type) { - case *gremlingo.Vertex: - if fmt.Sprint(*k.(*gremlingo.Vertex)) == fmt.Sprint(*ke.(*gremlingo.Vertex)) { - containsKey = true - } - default: - // Not equal. - } - default: - // If we are here we probably need to implement an additional type like the Vertex above. - if fmt.Sprint(*k.(*interface{})) == fmt.Sprint(*ke.(*interface{})) { - fmt.Println("WARNING: Encountered unknown pointer type as map key.") - containsKey = true - } - } - if containsKey { - e = ee - break - } - } } } if !containsKey { @@ -629,61 +613,172 @@ func compareMapEquals(expected map[interface{}]interface{}, actual map[interface return false } - if a == nil && e == nil { - continue - } else if a == nil || e == nil { - // One value is nil, other is not. They are not equal. - fmt.Printf("Map comparison error: One map has a nil key, other does not.\n") + if !compareSingleEqual(a, e) { + fmt.Printf("Map comparison error: Expected != Actual (%v!=%v)\n", e, a) return false - } else { - switch reflect.TypeOf(a).Kind() { - case reflect.Array, reflect.Slice: - switch reflect.TypeOf(e).Kind() { - case reflect.Array, reflect.Slice: - // Compare arrays - if !compareListEqualsWithoutOrder(e.([]interface{}), a.([]interface{})) { - return false - } - default: - fmt.Printf("Map comparison error: Expected type is Array/Slice, actual is %s.\n", reflect.TypeOf(a).Kind()) - return false - } - case reflect.Map: - switch reflect.TypeOf(a).Kind() { - case reflect.Map: - // Compare maps - if !compareMapEquals(e.(map[interface{}]interface{}), a.(map[interface{}]interface{})) { - return false - } - default: - fmt.Printf("Map comparison error: Expected type is Map, actual is %s.\n", reflect.TypeOf(a).Kind()) - return false - } - default: - if fmt.Sprint(a) != fmt.Sprint(e) { - fmt.Printf("Map comparison error: Expected != Actual (%s!=%s)\n", fmt.Sprint(a), fmt.Sprint(e)) - return false - } - } } } return true } -func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{}) bool { - // This is a little weird, but there isn't a good solution to either of these problems: - // 1. Comparison of types in Go. No deep equals which actually works properly. Needs to be done manually. - // 2. In place deletion in a loop. - // So to do in place deletion in a loop we can do the following: - // 1. Loop from back to wrong (don't need to worry about deleted indices that way. - // 2. Create a new slice with the index removed when we fix the item we want to delete. - // To do an orderless copy, a copy of the expected result is created. Results are removed as they are found. This stops - // the following from returning equal [1 2 2 2] and [1 1 1 2] - - // Shortcut. - if fmt.Sprint(expected) == fmt.Sprint(actual) { +// compareSingleEqual is the core logical equality function used for Gremlin Gherkin tests. +// It avoids using fmt.Sprint which can include memory addresses for pointers (like Vertices or Edges), +// leading to false negatives in tests. It performs recursive comparisons for complex types +// and handles Gremlin-specific elements by comparing their IDs. +func compareSingleEqual(actual interface{}, expected interface{}) bool { + if actual == nil && expected == nil { return true + } else if actual == nil || expected == nil { + return false } + + actualSet, ok1 := actual.(*gremlingo.SimpleSet) + expectedSet, ok2 := expected.(*gremlingo.SimpleSet) + if ok1 && ok2 { + return compareListEqualsWithoutOrder(expectedSet.ToSlice(), actualSet.ToSlice()) + } else if ok1 || ok2 { + return false + } + + actualPath, ok1 := actual.(*gremlingo.Path) + expectedPath, ok2 := expected.(*gremlingo.Path) + if ok1 && ok2 { + return compareListEqualsWithOrder(expectedPath.Objects, actualPath.Objects) + } else if ok1 || ok2 { + return false + } + + actualV, ok1 := actual.(*gremlingo.Vertex) + expectedV, ok2 := expected.(*gremlingo.Vertex) + if ok1 && ok2 { + return actualV.Id == expectedV.Id + } + + actualE, ok1 := actual.(*gremlingo.Edge) + expectedE, ok2 := expected.(*gremlingo.Edge) + if ok1 && ok2 { + return actualE.Id == expectedE.Id + } + + actualVP, ok1 := actual.(*gremlingo.VertexProperty) + expectedVP, ok2 := expected.(*gremlingo.VertexProperty) + if ok1 && ok2 { + return actualVP.Id == expectedVP.Id + } + + actualP, ok1 := actual.(*gremlingo.Property) + expectedP, ok2 := expected.(*gremlingo.Property) + if ok1 && ok2 { + return actualP.Key == expectedP.Key && compareSingleEqual(actualP.Value, expectedP.Value) + } + + switch a := actual.(type) { + case map[interface{}]interface{}: + e, ok := expected.(map[interface{}]interface{}) + if !ok { + return false + } + return compareMapEquals(e, a) + case []interface{}: + e, ok := expected.([]interface{}) + if !ok { + return false + } + return compareListEqualsWithOrder(e, a) + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: + return compareNumeric(actual, expected) + default: + return fmt.Sprint(actual) == fmt.Sprint(expected) + } +} + +// compareNumeric normalizes different numeric types to float64 to allow for comparison +// between different precisions and representations (e.g. int64 vs float32). +// It also explicitly handles NaN equality which is required for some Gremlin tests. +func compareNumeric(actual interface{}, expected interface{}) bool { + var aVal, eVal float64 + switch a := actual.(type) { + case int: + aVal = float64(a) + case int8: + aVal = float64(a) + case int16: + aVal = float64(a) + case int32: + aVal = float64(a) + case int64: + aVal = float64(a) + case uint: + aVal = float64(a) + case uint8: + aVal = float64(a) + case uint16: + aVal = float64(a) + case uint32: + aVal = float64(a) + case uint64: + aVal = float64(a) + case float32: + aVal = float64(a) + case float64: + aVal = a + default: + return fmt.Sprint(actual) == fmt.Sprint(expected) + } + + switch e := expected.(type) { + case int: + eVal = float64(e) + case int8: + eVal = float64(e) + case int16: + eVal = float64(e) + case int32: + eVal = float64(e) + case int64: + eVal = float64(e) + case uint: + eVal = float64(e) + case uint8: + eVal = float64(e) + case uint16: + eVal = float64(e) + case uint32: + eVal = float64(e) + case uint64: + eVal = float64(e) + case float32: + eVal = float64(e) + case float64: + eVal = e + default: + return fmt.Sprint(actual) == fmt.Sprint(expected) + } + + if math.IsNaN(aVal) && math.IsNaN(eVal) { + return true + } + return aVal == eVal +} + +// compareListEqualsWithOrder compares two slices for equality, ensuring that they have +// the same length and that elements at each index are logically equal. +func compareListEqualsWithOrder(expected []interface{}, actual []interface{}) bool { + if len(expected) != len(actual) { + return false + } + for i := range actual { + if !compareSingleEqual(actual[i], expected[i]) { + return false + } + } + return true +} + +// compareListEqualsWithoutOrder compares two slices for equality regardless of order. +// It ensures they have the same length and that every element in the actual slice +// has a corresponding logically equal element in the expected slice. +func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{}) bool { if len(expected) != len(actual) { return false } @@ -691,71 +786,11 @@ func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{}) copy(expectedCopy, expected) for _, a := range actual { found := false - if a == nil { - for i := len(expectedCopy) - 1; i >= 0; i-- { - if expectedCopy[i] == nil { - expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) - found = true - break - } - } - } else if actualSet, ok := a.(*gremlingo.SimpleSet); ok { - // Set is a special case here because there is no TypeOf().Kind() for sets. - actualStringArray := makeSortedStringArrayFromSet(actualSet) - - for i := len(expectedCopy) - 1; i >= 0; i-- { - curExpected := expectedCopy[i] - expectedSet, ok := curExpected.(*gremlingo.SimpleSet) - if ok { - expectedStringArray := makeSortedStringArrayFromSet(expectedSet) - - if reflect.DeepEqual(actualStringArray, expectedStringArray) { - expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) - found = true - break - } - } - } - } else { - switch reflect.TypeOf(a).Kind() { - case reflect.Array, reflect.Slice: - for i := len(expectedCopy) - 1; i >= 0; i-- { - if expectedCopy[i] != nil { - switch reflect.TypeOf(expectedCopy[i]).Kind() { - case reflect.Array, reflect.Slice: - if compareListEqualsWithoutOrder(expectedCopy[i].([]interface{}), a.([]interface{})) { - expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) - found = true - } - } - if found { - break - } - } - } - case reflect.Map: - for i := len(expectedCopy) - 1; i >= 0; i-- { - if expectedCopy[i] != nil { - switch reflect.TypeOf(expectedCopy[i]).Kind() { - case reflect.Map: - if compareMapEquals(expectedCopy[i].(map[interface{}]interface{}), a.(map[interface{}]interface{})) { - expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) - found = true - } - } - if found { - break - } - } - } - default: - for i := len(expectedCopy) - 1; i >= 0; i-- { - if fmt.Sprint(a) == fmt.Sprint(expectedCopy[i]) { - expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) - found = true - break - } - } + for i := len(expectedCopy) - 1; i >= 0; i-- { + if compareSingleEqual(a, expectedCopy[i]) { + expectedCopy = append(expectedCopy[:i], expectedCopy[i+1:]...) + found = true + break } } if !found { @@ -766,55 +801,17 @@ func compareListEqualsWithoutOrder(expected []interface{}, actual []interface{}) return true } +// compareListEqualsWithOf compares two slices for "of" equality. +// This is used for Gherkin "of" assertions where the actual result must be +// a subset of the expected results or vice versa depending on the context, +// but generally confirms that all elements in 'actual' exist in 'expected'. func compareListEqualsWithOf(expected []interface{}, actual []interface{}) bool { - // When comparing with "of", we expect cases like [1 2] (expected) and [1 1 1 2] (actual) , or - // [1 1 1 2] (expected) and [1 2] (actual) to return equal. for _, a := range actual { found := false - if a == nil { - for i := len(expected) - 1; i >= 0; i-- { - if expected[i] == nil { - found = true - break - } - } - } else { - switch reflect.TypeOf(a).Kind() { - case reflect.Array, reflect.Slice: - for i := len(expected) - 1; i >= 0; i-- { - if expected[i] != nil { - switch reflect.TypeOf(expected[i]).Kind() { - case reflect.Array, reflect.Slice: - if compareListEqualsWithoutOrder(expected[i].([]interface{}), a.([]interface{})) { - found = true - } - } - if found { - break - } - } - } - case reflect.Map: - for i := len(expected) - 1; i >= 0; i-- { - if expected[i] != nil { - switch reflect.TypeOf(expected[i]).Kind() { - case reflect.Map: - if compareMapEquals(expected[i].(map[interface{}]interface{}), a.(map[interface{}]interface{})) { - found = true - } - } - if found { - break - } - } - } - default: - for i := len(expected) - 1; i >= 0; i-- { - if fmt.Sprint(a) == fmt.Sprint(expected[i]) { - found = true - break - } - } + for i := len(expected) - 1; i >= 0; i-- { + if compareSingleEqual(a, expected[i]) { + found = true + break } } if !found { diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 5025cfe516..d12f9fd6ed 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -1858,8 +1858,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_V_properties_order_id": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Properties().Order().Id()}}, "g_E_properties_order_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "alice").As("a").AddE("self").From("a").To("a").Property("weight", 0.5).Property("a", int32(10)).AddE("self").From("a").To("a").Property("weight", 1.0).Property("a", int32(11)).AddE("self").From("a").To("a").Property("weight", 0.4).Property("a", int32(12)).AddE("self").From("a").To("a").Property("weight", 1.0).Property("a [...] "g_E_properties_order_byXdescX_value": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.AddV("person").Property("name", "alice").As("a").AddE("self").From("a").To("a").Property("weight", 0.5).Property("a", int32(10)).AddE("self").From("a").To("a").Property("weight", 1.0).Property("a", int32(11)).AddE("self").From("a").To("a").Property("weight", 0.4).Property("a", int32(12)).AddE("self").From("a").To("a").Property("weight", 1.0).Pr [...] - "g_inject_order": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("zzz", "foo", []interface{}{"a", "b", "c", "d"}, 1, []interface{}{"a", "b", "c"}, map[interface{}]interface{}{"a": "a", "b": "b" }, nil, 2.0, map[interface{}]interface{}{"a": "a", "b": false, "c": "c" }, "bar", true, false, math.Inf(1), math.NaN(), math.Inf(-1)).Order()}}, - "g_inject_order_byXdescX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("zzz", "foo", []interface{}{"a", "b", "c", "d"}, 1, []interface{}{"a", "b", "c"}, map[interface{}]interface{}{"a": "a", "b": "b" }, nil, 2.0, map[interface{}]interface{}{"a": "a", "b": false, "c": "c" }, "bar", true, false, math.Inf(1), math.NaN(), math.Inf(-1)).Order().By(gremlingo.Order.Desc)}}, + "g_inject_order": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("zzz", "foo", uuid.MustParse("6100808b-62f9-42b7-957e-ed66c30f40d1"), []interface{}{"a", "b", "c", "d"}, 1, time.Date(2023, 8, 1, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)), []interface{}{"a", "b", "c"}, map[interface{}]interface{}{"a": "a", "b": "b" }, nil, 2.0, time.Date(2023, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)), gremlingo.NewSimpleSet("x", [...] + "g_inject_order_byXdescX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("zzz", "foo", uuid.MustParse("6100808b-62f9-42b7-957e-ed66c30f40d1"), []interface{}{"a", "b", "c", "d"}, 1, time.Date(2023, 8, 1, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)), []interface{}{"a", "b", "c"}, map[interface{}]interface{}{"a": "a", "b": "b" }, nil, 2.0, time.Date(2023, 1, 1, 0, 0, 0, 0, time.FixedZone("UTC+00:00", 0)), gremlingo.NewSimple [...] "g_V_out_out_order_byXascX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Out().Order().By(gremlingo.Order.Asc)}}, "g_V_out_out_order_byXdescX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Out().Order().By(gremlingo.Order.Desc)}}, "g_V_out_out_asXheadX_path_order_byXascX_selectXheadX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Out().As("head").Path().Order().By(gremlingo.Order.Asc).Select("head")}}, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js index 89ac4c817e..d87ae803b1 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js @@ -27,6 +27,7 @@ const {Given, Then, When, setDefaultTimeout} = require('cucumber'); setDefaultTimeout(10 * 1000); const chai = require('chai') chai.use(require('chai-string')); +const uuid = require('uuid'); const expect = chai.expect; const util = require('util'); const gremlin = require('./gremlin').gremlin; @@ -76,6 +77,7 @@ const ignoreReason = { floatingPointIssues: "Javascript floating point numbers not working in this case", subgraphStepNotSupported: "Javascript does not yet support subgraph()", treeStepNotSupported: "Javascript does not yet support tree()", + uuidSerializationIssues: "Javascript does not serialize to a UUID object, which complicates test assertions", needsFurtherInvestigation: '', }; @@ -102,6 +104,9 @@ const ignoredScenarios = { // floating point issues 'g_withSackXBigInteger_TEN_powX1000X_assignX_V_localXoutXknowsX_barrierXnormSackXX_inXknowsX_barrier_sack': new IgnoreError(ignoreReason.floatingPointIssues), 'g_withSackX2X_V_sackXdivX_byXconstantX4_0XX_sack': new IgnoreError(ignoreReason.floatingPointIssues), + // uuid issues + 'g_inject_order_byXdescX': new IgnoreError(ignoreReason.uuidSerializationIssues), + 'g_inject_order': new IgnoreError(ignoreReason.uuidSerializationIssues), }; Given(/^the (.+) graph$/, function (graphName) { @@ -425,7 +430,7 @@ function toDateTime(value) { } function toUuid(value) { - return value; + return uuid.parse(value); } function toMerge(value) { diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js index 4bd3cc242f..6417f55d0c 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js @@ -315,15 +315,15 @@ const gremlins = { g_V_valuesXintX_asNumberXGType_SHORTX_isXtypeOfXGType_SHORTXX_maxX: [function({g}) { return g.addV("data").property("int", 15).addV("data").property("int", 25).addV("data").property("int", 35) }, function({g}) { return g.V().values("int").asNumber(GType.short).is(P.typeOf(GType.short)).max() }], g_injectX42X_asNumberXGType_SHORTX_isXtypeOfXGType_SHORTXX_storeXaX_capXaX: [function({g}) { return g.inject(42).asNumber(GType.short).is(P.typeOf(GType.short)) }], g_V_valuesXageX_isXtypeOfXGType_SHORTXX: [function({g}) { return g.V().values("age").is(P.typeOf(GType.short)) }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)) }], - g_V_hasXuuid_typeOfXGType_UUIDXX_valuesXnameX: [function({g}) { return g.addV("data").property("name", "test").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().has("uuid", P.typeOf(GType.uuid)).values("name") }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_project_byXidentityX_byXconstantXuuidXX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).project("original", "type").by(__.identity()).by(__.constant("uuid")) }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_whereXisXeqXuuidXX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).where(__.is(P.eq("f47af10b-58cc-4372-a567-0f02b2f3d479"))) }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_chooseXisXeqXuuidXX_constantXmatchX_constantXnoMatchXX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).choose(__.is(P.eq("f47af10b-58cc-4372-a567-0f02b2f3d479")), __.constant("match"), __.constant("noMatch")) }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_localXaggregateXaXX_capXaX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).local(__.aggregate("a")).cap("a") }], - g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_aggregateXaX_capXaX: [function({g}) { return g.addV("data").property("uuid", "f47af10b-58cc-4372-a567-0f02b2f3d479") }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).aggregate("a").cap("a") }], - g_injectXuuidX_isXtypeOfXGType_UUIDXX_groupCount: [function({g}) { return g.inject("f47af10b-58cc-4372-a567-0f02b2f3d479").is(P.typeOf(GType.uuid)).groupCount() }], - g_injectXUUIDX47af10b_58cc_4372_a567_0f02b2f3d479XX: [function({g}) { return g.inject("f47af10b-58cc-4372-a567-0f02b2f3d479") }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)) }], + g_V_hasXuuid_typeOfXGType_UUIDXX_valuesXnameX: [function({g}) { return g.addV("data").property("name", "test").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().has("uuid", P.typeOf(GType.uuid)).values("name") }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_project_byXidentityX_byXconstantXuuidXX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).project("original", "type").by(__.identity()).by(__.constant("uuid")) }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_whereXisXeqXuuidXX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).where(__.is(P.eq(uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")))) }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_chooseXisXeqXuuidXX_constantXmatchX_constantXnoMatchXX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).choose(__.is(P.eq(uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479"))), __.constant("match"), __.constant("noMatch")) }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_localXaggregateXaXX_capXaX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).local(__.aggregate("a")).cap("a") }], + g_V_valuesXuuidX_isXtypeOfXGType_UUIDXX_aggregateXaX_capXaX: [function({g}) { return g.addV("data").property("uuid", uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }, function({g}) { return g.V().values("uuid").is(P.typeOf(GType.uuid)).aggregate("a").cap("a") }], + g_injectXuuidX_isXtypeOfXGType_UUIDXX_groupCount: [function({g}) { return g.inject(uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")).is(P.typeOf(GType.uuid)).groupCount() }], + g_injectXUUIDX47af10b_58cc_4372_a567_0f02b2f3d479XX: [function({g}) { return g.inject(uuid.parse("f47af10b-58cc-4372-a567-0f02b2f3d479")) }], g_injectXUUIDXXX: [function({g}) { return g.inject(uuid.v4()) }], g_V_aggregateXxX_byXnameX_byXageX_capXxX: [function({g}) { return g.V().aggregate("x").by("name").by("age").cap("x") }], g_V_localXaggregateXxX_byXnameXX_byXageX_capXxX: [function({g}) { return g.V().local(__.aggregate("x").by("name").by("age")).cap("x") }], @@ -1889,8 +1889,8 @@ const gremlins = { g_V_properties_order_id: [function({g}) { return g.V().properties().order().id() }], g_E_properties_order_value: [function({g}) { return g.addV("person").property("name", "alice").as("a").addE("self").from_("a").to("a").property("weight", 0.5).property("a", 10).addE("self").from_("a").to("a").property("weight", 1.0).property("a", 11).addE("self").from_("a").to("a").property("weight", 0.4).property("a", 12).addE("self").from_("a").to("a").property("weight", 1.0).property("a", 13).addE("self").from_("a").to("a").property("weight", 0.4).property("a", 14).addE("self").fr [...] g_E_properties_order_byXdescX_value: [function({g}) { return g.addV("person").property("name", "alice").as("a").addE("self").from_("a").to("a").property("weight", 0.5).property("a", 10).addE("self").from_("a").to("a").property("weight", 1.0).property("a", 11).addE("self").from_("a").to("a").property("weight", 0.4).property("a", 12).addE("self").from_("a").to("a").property("weight", 1.0).property("a", 13).addE("self").from_("a").to("a").property("weight", 0.4).property("a", 14).addE(" [...] - g_inject_order: [function({g}) { return g.inject("zzz", "foo", ["a", "b", "c", "d"], 1, ["a", "b", "c"], new Map([["a", "a"], ["b", "b"]]), null, 2.0, new Map([["a", "a"], ["b", false], ["c", "c"]]), "bar", true, false, Number.POSITIVE_INFINITY, Number.NaN, Number.NEGATIVE_INFINITY).order() }], - g_inject_order_byXdescX: [function({g}) { return g.inject("zzz", "foo", ["a", "b", "c", "d"], 1, ["a", "b", "c"], new Map([["a", "a"], ["b", "b"]]), null, 2.0, new Map([["a", "a"], ["b", false], ["c", "c"]]), "bar", true, false, Number.POSITIVE_INFINITY, Number.NaN, Number.NEGATIVE_INFINITY).order().by(Order.desc) }], + g_inject_order: [function({g}) { return g.inject("zzz", "foo", uuid.parse("6100808b-62f9-42b7-957e-ed66c30f40d1"), ["a", "b", "c", "d"], 1, new Date('2023-08-01T00:00Z'), ["a", "b", "c"], new Map([["a", "a"], ["b", "b"]]), null, 2.0, new Date('2023-01-01T00:00Z'), new Set(["x", "y", "z"]), new Map([["a", "a"], ["b", false], ["c", "c"]]), "bar", uuid.parse("5100808b-62f9-42b7-957e-ed66c30f40d1"), true, false, Number.POSITIVE_INFINITY, Number.NaN, Number.NEGATIVE_INFINITY).order() }], + g_inject_order_byXdescX: [function({g}) { return g.inject("zzz", "foo", uuid.parse("6100808b-62f9-42b7-957e-ed66c30f40d1"), ["a", "b", "c", "d"], 1, new Date('2023-08-01T00:00Z'), ["a", "b", "c"], new Map([["a", "a"], ["b", "b"]]), null, 2.0, new Date('2023-01-01T00:00Z'), new Set(["x", "y", "z"]), new Map([["a", "a"], ["b", false], ["c", "c"]]), "bar", uuid.parse("5100808b-62f9-42b7-957e-ed66c30f40d1"), true, false, Number.POSITIVE_INFINITY, Number.NaN, Number.NEGATIVE_INFINITY).ord [...] g_V_out_out_order_byXascX: [function({g}) { return g.V().out().out().order().by(Order.asc) }], g_V_out_out_order_byXdescX: [function({g}) { return g.V().out().out().order().by(Order.desc) }], g_V_out_out_asXheadX_path_order_byXascX_selectXheadX: [function({g}) { return g.V().out().out().as("head").path().order().by(Order.asc).select("head") }], diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index 9b12dda8cd..82ea6f48b9 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -1861,8 +1861,8 @@ world.gremlins = { 'g_V_properties_order_id': [(lambda g:g.V().properties().order().id_())], 'g_E_properties_order_value': [(lambda g:g.add_v('person').property('name', 'alice').as_('a').add_e('self').from_('a').to('a').property('weight', 0.5).property('a', 10).add_e('self').from_('a').to('a').property('weight', 1.0).property('a', 11).add_e('self').from_('a').to('a').property('weight', 0.4).property('a', 12).add_e('self').from_('a').to('a').property('weight', 1.0).property('a', 13).add_e('self').from_('a').to('a').property('weight', 0.4).property('a', 14).add_e('self').from_ [...] 'g_E_properties_order_byXdescX_value': [(lambda g:g.add_v('person').property('name', 'alice').as_('a').add_e('self').from_('a').to('a').property('weight', 0.5).property('a', 10).add_e('self').from_('a').to('a').property('weight', 1.0).property('a', 11).add_e('self').from_('a').to('a').property('weight', 0.4).property('a', 12).add_e('self').from_('a').to('a').property('weight', 1.0).property('a', 13).add_e('self').from_('a').to('a').property('weight', 0.4).property('a', 14).add_e('sel [...] - 'g_inject_order': [(lambda g:g.inject('zzz', 'foo', ['a', 'b', 'c', 'd'], 1, ['a', 'b', 'c'], { 'a': 'a', 'b': 'b' }, None, 2.0, { 'a': 'a', 'b': False, 'c': 'c' }, 'bar', True, False, float('inf'), float('nan'), float('-inf')).order())], - 'g_inject_order_byXdescX': [(lambda g:g.inject('zzz', 'foo', ['a', 'b', 'c', 'd'], 1, ['a', 'b', 'c'], { 'a': 'a', 'b': 'b' }, None, 2.0, { 'a': 'a', 'b': False, 'c': 'c' }, 'bar', True, False, float('inf'), float('nan'), float('-inf')).order().by(Order.desc))], + 'g_inject_order': [(lambda g:g.inject('zzz', 'foo', uuid.UUID('6100808b-62f9-42b7-957e-ed66c30f40d1'), ['a', 'b', 'c', 'd'], 1, datetime.datetime.fromisoformat('2023-08-01T00:00+00:00'), ['a', 'b', 'c'], { 'a': 'a', 'b': 'b' }, None, 2.0, datetime.datetime.fromisoformat('2023-01-01T00:00+00:00'), {'x', 'y', 'z'}, { 'a': 'a', 'b': False, 'c': 'c' }, 'bar', uuid.UUID('5100808b-62f9-42b7-957e-ed66c30f40d1'), True, False, float('inf'), float('nan'), float('-inf')).order())], + 'g_inject_order_byXdescX': [(lambda g:g.inject('zzz', 'foo', uuid.UUID('6100808b-62f9-42b7-957e-ed66c30f40d1'), ['a', 'b', 'c', 'd'], 1, datetime.datetime.fromisoformat('2023-08-01T00:00+00:00'), ['a', 'b', 'c'], { 'a': 'a', 'b': 'b' }, None, 2.0, datetime.datetime.fromisoformat('2023-01-01T00:00+00:00'), {'x', 'y', 'z'}, { 'a': 'a', 'b': False, 'c': 'c' }, 'bar', uuid.UUID('5100808b-62f9-42b7-957e-ed66c30f40d1'), True, False, float('inf'), float('nan'), float('-inf')).order().by(Ord [...] 'g_V_out_out_order_byXascX': [(lambda g:g.V().out().out().order().by(Order.asc))], 'g_V_out_out_order_byXdescX': [(lambda g:g.V().out().out().order().by(Order.desc))], 'g_V_out_out_asXheadX_path_order_byXascX_selectXheadX': [(lambda g:g.V().out().out().as_('head').path().order().by(Order.asc).select('head'))], diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java index c9c8ca955c..4c9dad80dd 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/OrderabilityTest.java @@ -38,10 +38,8 @@ import static org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexFeatur import static org.apache.tinkerpop.gremlin.structure.Graph.Features.VertexPropertyFeatures; import java.time.OffsetDateTime; -import java.time.ZoneId; import java.time.ZoneOffset; import java.util.Arrays; -import java.util.Date; import java.util.List; import java.util.Map; import java.util.Set; @@ -186,12 +184,12 @@ public abstract class OrderabilityTest extends AbstractGremlinProcessTest { null, false, true, 1, 2.0, + Constants.date, "bar", "foo", Constants.uuid, Constants.set1, Constants.set2, Constants.list1, Constants.list2, - Constants.map1, Constants.map2, - Constants.date + Constants.map1, Constants.map2 ), traversal); } @@ -212,13 +210,13 @@ public abstract class OrderabilityTest extends AbstractGremlinProcessTest { null, false, true, 1, 2.0, + Constants.date, "bar", "foo", Constants.uuid, Constants.set1, Constants.set2, Constants.list1, Constants.list2, Constants.map1, Constants.map2, - unknown, - Constants.date + unknown ), traversal); } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature index 8b2b0ca289..0079aac13f 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Orderability.feature @@ -153,11 +153,9 @@ Feature: Orderability @GraphComputerVerificationInjectionNotSupported Scenario: g_inject_order Given the empty graph - - # TODO add support for Set, UUID, Date once the framework supports it And the traversal of """ - g.inject("zzz","foo",["a","b","c","d"],1,["a","b","c"],[a:"a",b:"b"],null,2.0d,[a:"a",b:false,c:"c"],"bar",true,false,Infinity,NaN,-Infinity).order() + g.inject("zzz","foo",UUID("6100808b-62f9-42b7-957e-ed66c30f40d1"),["a","b","c","d"],1,datetime("2023-08-01T00:00:00Z"),["a","b","c"],[a:"a",b:"b"],null,2.0d,datetime("2023-01-01T00:00:00Z"),{"x","y","z"},[a:"a",b:false,c:"c"],"bar",UUID("5100808b-62f9-42b7-957e-ed66c30f40d1"),true,false,Infinity,NaN,-Infinity).order() """ When iterated to list Then the result should be ordered @@ -170,9 +168,14 @@ Feature: Orderability | d[2.0].d | | d[Infinity] | | d[NaN] | + | dt[2023-01-01T00:00:00Z] | + | dt[2023-08-01T00:00:00Z] | | bar | | foo | | zzz | + | uuid[5100808b-62f9-42b7-957e-ed66c30f40d1] | + | uuid[6100808b-62f9-42b7-957e-ed66c30f40d1] | + | s[x,y,z] | | l[a,b,c] | | l[a,b,c,d] | | m[{"a":"a", "b":false, "c":"c"}] | @@ -181,10 +184,9 @@ Feature: Orderability @GraphComputerVerificationInjectionNotSupported Scenario: g_inject_order_byXdescX Given the empty graph - # TODO add support for Set, UUID, Date once the framework supports it And the traversal of """ - g.inject("zzz","foo",["a","b","c","d"],1,["a","b","c"],[a:"a",b:"b"],null,2.0d,[a:"a",b:false,c:"c"],"bar",true,false,Infinity,NaN,-Infinity).order().by(desc) + g.inject("zzz","foo",UUID("6100808b-62f9-42b7-957e-ed66c30f40d1"),["a","b","c","d"],1,datetime("2023-08-01T00:00:00Z"),["a","b","c"],[a:"a",b:"b"],null,2.0d,datetime("2023-01-01T00:00:00Z"),{"x","y","z"},[a:"a",b:false,c:"c"],"bar",UUID("5100808b-62f9-42b7-957e-ed66c30f40d1"),true,false,Infinity,NaN,-Infinity).order().by(desc) """ When iterated to list Then the result should be ordered @@ -193,9 +195,14 @@ Feature: Orderability | m[{"a":"a", "b":false, "c":"c"}] | | l[a,b,c,d] | | l[a,b,c] | + | s[x,y,z] | + | uuid[6100808b-62f9-42b7-957e-ed66c30f40d1] | + | uuid[5100808b-62f9-42b7-957e-ed66c30f40d1] | | zzz | | foo | | bar | + | dt[2023-08-01T00:00:00Z] | + | dt[2023-01-01T00:00:00Z] | | d[NaN] | | d[Infinity] | | d[2.0].d | diff --git a/pom.xml b/pom.xml index 701031edb4..5f665f4ffe 100644 --- a/pom.xml +++ b/pom.xml @@ -544,6 +544,7 @@ limitations under the License. <exclude>**/_site/**</exclude> <exclude>**/.pytest_cache/**</exclude> <exclude>**/venv/**</exclude> + <exclude>**/.venv/**</exclude> <exclude>**/.eggs/**</exclude> <exclude>**/gremlinpython.egg-info/**</exclude> <exclude>**/docfx/**</exclude>
