This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch type-enum-poc in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit b59199b8f8af133650fcbfb245f118feb5638b62 Merge: 13a51e9dfb d6b80bd724 Author: Yang Xia <[email protected]> AuthorDate: Mon Jul 28 19:41:55 2025 -0700 Merge branch '3.8-dev' into TINKERPOP-3166_asNumber .github/workflows/build-test.yml | 14 - CHANGELOG.asciidoc | 22 +- docker/Dockerfile | 5 +- docs/site/home/download.html | 2 +- .../dev/developer/development-environment.asciidoc | 4 +- docs/src/dev/provider/gremlin-semantics.asciidoc | 26 ++ docs/src/reference/gremlin-applications.asciidoc | 21 +- docs/src/reference/gremlin-variants.asciidoc | 20 +- .../reference/implementations-tinkergraph.asciidoc | 4 +- docs/src/reference/the-traversal.asciidoc | 23 +- docs/src/tutorials/getting-started/index.asciidoc | 6 +- docs/src/upgrade/release-3.7.x.asciidoc | 5 + docs/src/upgrade/release-3.8.x.asciidoc | 309 ++++++++------ gremlin-console/src/main/bin/gremlin-java8.bat | 59 --- gremlin-console/src/main/bin/gremlin.sh | 1 + .../tinkerpop/gremlin/console/Console.groovy | 3 +- .../gremlin/console/GremlinGroovysh.groovy | 81 +--- .../gremlin/console/LocalSafeParser.groovy | 146 +++++++ .../gremlin/console/GremlinGroovyshTest.groovy | 26 +- .../gremlin/console/LocalSafeParserTest.groovy | 186 +++++++++ .../grammar/DefaultGremlinBaseVisitor.java | 4 + .../language/grammar/TraversalMethodVisitor.java | 8 + .../traversal/dsl/graph/GraphTraversal.java | 14 + .../gremlin/process/traversal/dsl/graph/__.java | 7 + .../process/traversal/step/map/AsBoolStep.java | 61 +++ .../process/traversal/step/map/CallStep.java | 22 +- .../process/traversal/util/BytecodeHelper.java | 2 + .../process/traversal/util/DefaultTraversal.java | 6 +- .../language/translator/GremlinTranslatorTest.java | 9 + .../process/traversal/step/map/AsBoolStepTest.java | 81 ++++ .../process/traversal/step/map/CallStepTest.java | 68 ++++ .../traversal/util/DefaultTraversalTest.java | 93 +++++ gremlin-dotnet/src/Gremlin.Net/Gremlin.Net.csproj | 2 +- .../Process/Traversal/GraphTraversal.cs | 9 + .../src/Gremlin.Net/Process/Traversal/__.cs | 8 + .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 15 + gremlin-go/docker-compose.yml | 2 +- gremlin-go/driver/anonymousTraversal.go | 7 +- gremlin-go/driver/client.go | 3 +- gremlin-go/driver/client_test.go | 32 ++ gremlin-go/driver/cucumber/gremlin.go | 15 + gremlin-go/driver/graphTraversal.go | 10 +- gremlin-go/examples/go.mod | 13 +- gremlin-go/examples/go.sum | 61 +-- gremlin-go/go.mod | 2 +- gremlin-javascript/examples/node/package-lock.json | 2 +- gremlin-javascript/examples/node/package.json | 2 +- gremlin-javascript/pom.xml | 4 +- .../gremlin-javascript/docker-compose.yml | 2 +- .../gremlin-javascript/lib/driver/connection.js | 3 +- .../lib/process/graph-traversal.js | 11 + .../gremlin-javascript/package-lock.json | 2 +- .../javascript/gremlin-javascript/package.json | 2 +- .../gremlin-javascript/test/cucumber/gremlin.js | 15 + .../gremlin-javascript/test/unit/client-test.js | 13 + gremlin-language/src/main/antlr4/Gremlin.g4 | 7 + .../gremlin_python/driver/aiohttp/transport.py | 2 + .../gremlin_python/process/graph_traversal.py | 14 + gremlin-python/src/main/python/radish/gremlin.py | 15 + .../apache/tinkerpop/gremlin/server/Settings.java | 11 + .../gremlin/server/channel/UnifiedChannelizer.java | 9 + .../gremlin/server/handler/AbstractSession.java | 3 + .../gremlin/server/handler/MultiTaskSession.java | 3 + .../gremlin/server/handler/SessionException.java | 3 + .../gremlin/server/handler/SessionTask.java | 3 + .../gremlin/server/handler/SingleTaskSession.java | 3 + .../gremlin/server/handler/UnifiedHandler.java | 3 + .../gremlin/server/GremlinDriverIntegrateTest.java | 47 +++ .../gremlin/test/features/map/AsBool.feature | 203 ++++++++++ .../gremlin/process/ApplyStrategiesBenchmark.java | 134 ++++++ .../gremlin/process/TraversalLockBenchmark.java | 135 ++++++ gremlint/package-lock.json | 2 +- gremlint/package.json | 2 +- gremlint/pom.xml | 4 +- pom.xml | 32 +- .../structure/TinkerElementContainer.java | 21 +- .../structure/TinkerTransactionGraph.java | 10 +- .../structure/TinkerTransactionGraphTest.java | 451 +++++++++++++++++++-- 78 files changed, 2247 insertions(+), 443 deletions(-) diff --cc CHANGELOG.asciidoc index c969413f17,4947b39a17..baa442c7d8 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@@ -76,8 -79,8 +79,9 @@@ This release also includes changes fro * Moved all lambda oriented Gremlin tests to `LambdaStepTest` in the Java test suite. * Removed the `@RemoteOnly` testing tag in Gherkin as lambda tests have all been moved to the Java test suite. * Updated gremlin-javascript to use GraphBinary as default instead of GraphSONv3 +* Added the `asNumber()` step to perform number conversion. * Renamed many types in the grammar for consistent use of terms "Literal", "Argument", and "Varargs" + * Changed `gremlin-net` so that System.Text.Json is only listed as an explicit dependency when it is not available from the framework. == TinkerPop 3.7.0 (Gremfir Master of the Pan Flute) diff --cc docs/src/upgrade/release-3.8.x.asciidoc index 2ca45e6322,4d996ad3bb..3574e7b8fa --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@@ -30,110 -30,48 +30,111 @@@ complete list of all the modifications === Upgrading for Users +==== Number Conversion Step + +We have been iteratively introducing new language features into Gremlin, with the most recent major additions being string, list and date manipulation +steps introduced in the 3.7 line. In 3.8.0, we are now introducing a number conversion step, `asNumber()`, to bridge +a gap in casting functionalities. + +The new `asNumber()` serves as an umbrella step that parses strings and casts numbers into desired types. For the convenience of remote traversals in GLVs, these number types are denoted by a set of number tokens (`N`). + +This new step will allow users to normalize their data by converting string numbers and mixed numeric types to a consistent format, making it easier to perform downstream mathematical operations. As an example: + +[source,text] +---- +// sum() step can only take numbers +gremlin> g.inject(1.0, 2l, 3, "4", "0x5").sum() +class java.lang.String cannot be cast to class java.lang.Number + +// use asNumber() to avoid casting exceptions +gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum() +==>15.0 + +// given sum() step returned a double, one can use asNumber() to further cast the result into desired type +gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum().asNumber(N.int_) +==>15 +---- + +Semantically, the `asNumber()` step will convert the incoming traverser to a logical parsable type if no argument is provided, or to the desired numerical type, based on the number token (`N`) provided. + +Numerical input will pass through unless a type is specified by the number token. `ArithmeticException` will be thrown for any overflow as a result of narrowing of types: + +[source,text] +---- +gremlin> g.inject(5.0).asNumber(N.int_) +==> 5 // casts double to int +gremlin> g.inject(12).asNumber(N.byte_) +==> 12 +gremlin> g.inject(128).asNumber(N.byte_) +==> ArithmeticException +---- + +String input will be parsed. By default, the smalled unit of number to be parsed into is `int` if no number token is provided. `NumberFormatException` will be thrown for any unparsable strings: + +[source,text] +---- +gremlin> g.inject("5").asNumber() +==> 5 +gremlin> g.inject("5.7").asNumber(N.int_) +==> 5 +gremlin> g.inject("1,000").asNumber(N.int_) +==> NumberFormatException +gremlin> g.inject("128").asNumber(N.byte_) +==> ArithmeticException +---- + +All other input types will result in `IllegalArgumentException`: +[source,text] +---- +gremlin> g.inject([1, 2, 3, 4]).asNumber() +==> IllegalArgumentException +---- + +See: link:https://tinkerpop.apache.org/docs/3.8.0/reference/#asNumber-step[asNumber()-step] +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3166[TINKERPOP-3166] + - ==== Removal of Vertex/ReferenceVertex from grammar + ==== Boolean Conversion Step - `StructureVertex`, previously used to construct new vertices in the grammar, now been removed. The `V()` step, as well - as the `from()` and `to()` modulators used with `addE()`, previously accepted `StructureVertex` as arguments in the - grammar. In its place, the `from()` and `to()` modulators can now directly accept a vertex id in place of a `Vertex` - when used with `addE()` (`V()` has always accepted ids in addition to vertices). When using these steps in `gremlin-lang` - scripts, the vertex id must be used directly. This change has no effect on the `GraphTraversal` API, nor on - `gremlin-groovy` scripts. Vertices can continue to be used directly such places. + The `asBool()` step bridges another gap in Gremlin's casting functionalities. Users now have the ability to parse strings and + numbers into boolean values, both for normalization and to perform boolean logic with numerical values. - [source,groovy] + [source,text] ---- - // 3.7.3 - gremlin> v1 = g.V(1).next() - ==>v[1] - gremlin> v2 = g.V(2).next() - ==>v[2] - gremlin> script = String.format("g.V(new Vertex(%s)).outE().where(inV().is(new Vertex(%s)))", v1.id(), v2.id()) - ==>g.V(new Vertex(1)).outE().where(inV().is(new Vertex(2))) - gremlin> client.submit(script).all().get().get(0).getEdge() - ==>e[7][1-knows->2] + gremlin> g.inject(2, "true", 1, 0, false, "FALSE").asBool().fold() + ==>[true,true,true,false,false,false] - // 3.8.0 - gremlin> v1 = g.V(1).next() - ==>v[1] - gremlin> v2 = g.V(2).next() - ==>v[2] - gremlin> script = String.format("g.V(%s).outE().where(inV().id().is(%s))", v1.id(), v2.id()) - ==>g.V(1).outE().where(inV().id().is(2)) - gremlin> client.submit(script).all().get().get(0).getEdge() - ==>e[7][1-knows->2] + // using the modern graph, we can turn count() results into boolean values + gremlin> g.V().local(outE().count()).fold() + ==>[3,0,0,2,0,1] + gremlin> g.V().local(outE().count()).asBool().fold() + ==>[true,false,false,true,false,true] + // a slightly more complex one using sack for boolean operations for vertices with both 'person' label and has out edges + gremlin> g.V().sack(assign).by(__.hasLabel('person').count().asBool()).sack(and).by(__.outE().count().asBool()).sack().path() + ==>[v[1],true] + ==>[v[2],false] + ==>[v[3],false] + ==>[v[4],true] + ==>[v[5],false] + ==>[v[6],true] ---- - ==== Auto promotion of number types + See: link:https://tinkerpop.apache.org/docs/3.8.0/reference/#asBool-step[asBool()-step] + See: link:https://issues.apache.org/jira/browse/TINKERPOP-3175[TINKERPOP-3175] - Previously, operations like sum or sack that involved mathematical calculations did not automatically promote the result - to a larger numeric type (e.g., from int to long) when needed. As a result, values could wrap around within their current - type, leading to unexpected behavior. This issue has now been resolved by enabling automatic type promotion for results. + ==== Set minimum Java version to 11 - Now, any mathematical operations such as Add, Sub, Mul, and Div will now automatically promote to the next numeric type - if an overflow is detected. For integers, the promotion sequence is: byte → short → int → long → overflow exception. For - floating-point numbers, the sequence is: float → double → infinity. + TinkerPop 3.8 requires a minimum of Java 11 for building and running. Support for Java 1.8 has been dropped. + + ==== Auto-promotion of Numbers + + Previously, operations like `sum` or `sack` that involved mathematical calculations did not automatically promote the + result to a larger numeric type (e.g., from int to long) when needed. As a result, values could wrap around within their + current type leading to unexpected behavior. This issue has now been resolved by enabling automatic type promotion for + results. + + Now, any mathematical operations such as `Add`, `Sub`, `Mul`, and `Div` will now automatically promote to the next + numeric type if an overflow is detected. For integers, the promotion sequence is: byte → short → int → long → overflow + exception. For floating-point numbers, the sequence is: float → double → infinity. The following example showcases the change in overflow behavior between 3.7.3 and 3.8.0 diff --cc gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java index aa3c2cfbc5,8470cb289a..2d70e4e155 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.java @@@ -83,8 -82,8 +83,9 @@@ import org.apache.tinkerpop.gremlin.pro import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStep; -import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsDateStep; + import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsBoolStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsDateStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsNumberStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsStringGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsStringLocalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep; diff --cc gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java index e9e2ac90da,0ad7c53764..1b0ce092bf --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/BytecodeHelper.java @@@ -64,8 -64,8 +64,9 @@@ import org.apache.tinkerpop.gremlin.pro import org.apache.tinkerpop.gremlin.process.traversal.step.filter.WhereTraversalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStep; + import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsBoolStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsDateStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsNumberStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsStringGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsStringLocalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.CallStep; diff --cc gremlin-go/driver/anonymousTraversal.go index 6cf32581bb,67b107ab07..dfc99e60f3 --- a/gremlin-go/driver/anonymousTraversal.go +++ b/gremlin-go/driver/anonymousTraversal.go @@@ -383,14 -383,14 +383,19 @@@ func (anonymousTraversal *anonymousTrav // AsDate adds the AsDate step to the GraphTraversal. func (anonymousTraversal *anonymousTraversal) AsDate(args ...interface{}) *GraphTraversal { - return anonymousTraversal.graphTraversal().AsDate(args...) + return anonymousTraversal.graphTraversal().AsDate(args...) + } + + // AsBool adds the AsBool step to the GraphTraversal. + func (anonymousTraversal *anonymousTraversal) AsBool(args ...interface{}) *GraphTraversal { + return anonymousTraversal.graphTraversal().AsBool(args...) } +// AsDate adds the AsNumber step to the GraphTraversal. +func (anonymousTraversal *anonymousTraversal) AsNumber(args ...interface{}) *GraphTraversal { + return anonymousTraversal.graphTraversal().AsNumber(args...) +} + // AsString adds the asString step to the GraphTraversal. func (anonymousTraversal *anonymousTraversal) AsString(args ...interface{}) *GraphTraversal { return anonymousTraversal.graphTraversal().AsString(args...) diff --cc gremlin-go/driver/graphTraversal.go index b6b165f22d,ae9aa7f262..2608d74cbd --- a/gremlin-go/driver/graphTraversal.go +++ b/gremlin-go/driver/graphTraversal.go @@@ -106,18 -106,18 +106,24 @@@ func (g *GraphTraversal) As(args ...int return g } + // AsBool adds the asBool step to the GraphTraversal. + func (g *GraphTraversal) AsBool(args ...interface{}) *GraphTraversal { + g.Bytecode.AddStep("asBool", args...) + return g + } + // AsDate adds the asDate step to the GraphTraversal. func (g *GraphTraversal) AsDate(args ...interface{}) *GraphTraversal { - g.Bytecode.AddStep("asDate", args...) - return g + g.Bytecode.AddStep("asDate", args...) + return g } +// AsNumber adds the asNumber step to the GraphTraversal. +func (g *GraphTraversal) AsNumber(args ...interface{}) *GraphTraversal { + g.Bytecode.AddStep("asNumber", args...) + return g +} + // AsString adds the asString step to the GraphTraversal. func (g *GraphTraversal) AsString(args ...interface{}) *GraphTraversal { g.Bytecode.AddStep("asString", args...) diff --cc gremlin-language/src/main/antlr4/Gremlin.g4 index 00031355f6,0b60761f35..a3544cfe71 --- a/gremlin-language/src/main/antlr4/Gremlin.g4 +++ b/gremlin-language/src/main/antlr4/Gremlin.g4 @@@ -1685,9 -1672,9 +1690,10 @@@ keywor | K_AND | K_ANY | K_AS + | K_ASBOOL | K_ASC | K_ASDATE + | K_ASNUMBER | K_ASSTRING | K_ASSIGN | K_BARRIER @@@ -1954,9 -1932,9 +1960,10 @@@ K_ALL: 'all' K_AND: 'and'; K_ANY: 'any'; K_AS: 'as'; + K_ASBOOL: 'asBool'; K_ASC: 'asc'; K_ASDATE: 'asDate'; +K_ASNUMBER: 'asNumber'; K_ASSTRING: 'asString'; K_ASSIGN: 'assign'; K_BARRIER: 'barrier'; diff --cc gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py index ced9fc1890,9a33d899fd..f2399ef42e --- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py +++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py @@@ -2425,10 -2425,10 +2437,12 @@@ statics.add_static('and_', and_ statics.add_static('as_', as_) + statics.add_static('as_bool', as_bool) + statics.add_static('as_date', as_date) +statics.add_static('as_number', as_number) + statics.add_static('as_string', as_string) statics.add_static('barrier', barrier)
