This is an automated email from the ASF dual-hosted git repository.
xiazcy pushed a commit to branch TINKERPOP-3166_asNumber
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
The following commit(s) were added to refs/heads/TINKERPOP-3166_asNumber by
this push:
new 01c2d78ec1 Minor tweak for string parsing and updated relevant docs
01c2d78ec1 is described below
commit 01c2d78ec1939750e5c2a7fc711217a2f1a823f6
Author: xiazcy <[email protected]>
AuthorDate: Mon Jul 7 17:55:22 2025 -0400
Minor tweak for string parsing and updated relevant docs
---
CHANGELOG.asciidoc | 1 +
docs/src/reference/the-traversal.asciidoc | 31 ++++++++++++++
docs/src/upgrade/release-3.8.x.asciidoc | 47 ++++++++++++++++++++++
.../process/traversal/step/map/AsNumberStep.java | 6 +--
.../traversal/step/map/AsNumberStepTest.java | 39 ++++++++++++++++++
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 2 +-
gremlin-go/driver/cucumber/gremlin.go | 2 +-
.../gremlin-javascript/test/cucumber/gremlin.js | 2 +-
gremlin-python/src/main/python/radish/gremlin.py | 2 +-
.../gremlin/test/features/map/AsNumber.feature | 8 ++--
10 files changed, 127 insertions(+), 13 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 36d0d90344..040f96ca9b 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -75,6 +75,7 @@ This release also includes changes from <<release-3-7-XXX,
3.7.XXX>>.
* 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()` steps to perform number conversion.
* Renamed many types in the grammar for consistent use of terms "Literal",
"Argument", and "Varargs"
== TinkerPop 3.7.0 (Gremfir Master of the Pan Flute)
diff --git a/docs/src/reference/the-traversal.asciidoc
b/docs/src/reference/the-traversal.asciidoc
index eaf36d2b96..e39425881c 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -829,6 +829,37 @@ g.inject(datetime("2023-08-24T00:00:00Z")).asDate() <3>
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asDate()++[`asDate()`]
+[[asNumber-step]]
+=== AsNumber Step
+
+The `asNumber()`-step (*map*) converts the incoming traverser to the nearest
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, with the exception that any float number will be converted into double.
`ArithmeticException` will be thrown for any overflow during narrowing of types.
+
+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.
+
+All other input types will result in `IllegalArgumentException`.
+
+[gremlin-groovy,modern]
+----
+g.inject(1).asNumber() <1>
+g.inject(1.76).asNumber() <2>
+g.inject(1.76).asNumber(N.nint) <3>
+g.inject("1b").asNumber() <4>
+g.inject(33550336).asNumber(N.nbyte) <5>
+----
+
+<1> An int will be passed through.
+<2> A double will be passed through.
+<3> A double is converted into an in.
+<4> String containing any character other than numerical ones will result in
`NumberFormatException`.
+<5> Narrowing of int to byte that overflows will throw `ArithmeticException`.
+
+*Additional References*
+
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asNumber()++[`asNumber()`]
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asNumber(org.apache.tinkerpop.gremlin.process.traversal.N)++[`asNumber(N)`]
+
[[barrier-step]]
=== Barrier Step
diff --git a/docs/src/upgrade/release-3.8.x.asciidoc
b/docs/src/upgrade/release-3.8.x.asciidoc
index c1de647afc..50dea6654b 100644
--- a/docs/src/upgrade/release-3.8.x.asciidoc
+++ b/docs/src/upgrade/release-3.8.x.asciidoc
@@ -30,6 +30,53 @@ complete list of all the modifications that are part of this
release.
=== Upgrading for Users
+==== Number Conversion Step
+
+We have introduced a number conversion step, `asNumber()`, which converts the
incoming traverser to the nearest 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, with the exception that any float number will be converted into double.
`ArithmeticException` will be thrown for any overflow during narrowing of types:
+
+[source,text]
+----
+gremlin> g.inject(5).asNumber()
+==> 5 // parses to int
+gremlin> g.inject(5.0).asNumber()
+==> 5 // parses to double
+gremlin> g.inject(5.123f).asNumber()
+==> 5.123 // will cast float to double
+// Narrowing of types may result in ArithmeticException due to overflow
+gremlin> g.inject(12).asNumber(N.byte)
+==> 12
+gremlin> g.inject(128).asNumber(N.byte)
+==> ArithmeticException
+gremlin> g.inject(300).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.nint)
+==> NumberFormatException
+gremlin> g.inject("128").asNumber(N.nbyte)
+==> 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]
+
==== Auto promotion of number types
Previously, operations like sum or sack that involved mathematical
calculations did not automatically promote the result
diff --git
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java
index 1fe1a52051..1fb093be11 100644
---
a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java
+++
b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java
@@ -106,11 +106,7 @@ public final class AsNumberStep<S> extends
ScalarMapStep<S, Number> {
return result;
}
BigInteger result = new BigInteger(value.trim());
- if (result.bitLength() <= 7) {
- return result.byteValue();
- } else if (result.bitLength() <= 15) {
- return result.shortValue();
- } else if (result.bitLength() <= 31) {
+ if (result.bitLength() <= 31) { // default to int if not
specified, smaller sizes need to be intentionally set
return result.intValue();
} else if (result.bitLength() <= 63) {
return result.longValue();
diff --git
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java
index a812aac827..396d4bd8e4 100644
---
a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java
+++
b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java
@@ -18,6 +18,7 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.step.map;
+import org.apache.tinkerpop.gremlin.process.traversal.N;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest;
@@ -26,6 +27,7 @@ import org.junit.Test;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.UUID;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
@@ -40,6 +42,43 @@ public class AsNumberStepTest extends StepTest {
@Test
public void testReturnTypes() {
assertEquals(1, __.__(1).asNumber().next());
+ assertEquals((byte) 1, __.__(1).asNumber(N.nbyte).next());
+ assertEquals(1, __.__(1.8).asNumber(N.nint).next());
+ assertEquals(1, __.__(1L).asNumber(N.nint).next());
+ assertEquals(1L, __.__(1L).asNumber().next());
+ assertEquals(1, __.__("1").asNumber(N.nint).next());
+ assertEquals(1, __.__("1").asNumber().next());
+ assertEquals((byte) 1, __.__("1").asNumber(N.nbyte).next());
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void shouldThrowExceptionWhenInvalidStringInput() {
+ __.__("This String is not a number").asNumber().next();
+ }
+
+ @Test(expected = NumberFormatException.class)
+ public void shouldThrowParseExceptionWithInvalidNumberStringInput() {
+ __.__("128abc").asNumber().next();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionWhenArrayInput() {
+ __.__(Arrays.asList(1, 2)).asNumber().next();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldThrowExceptionWhenUUIDInput() {
+ __.__(UUID.randomUUID()).asNumber().next();
+ }
+
+ @Test(expected = ArithmeticException.class)
+ public void shouldThrowOverflowExceptionWhenParsedNumberOverflows() {
+ __.__("128").asNumber(N.nbyte).next();
+ }
+
+ @Test(expected = ArithmeticException.class)
+ public void shouldThrowOverflowExceptionWhenCastNumberOverflows() {
+ __.__(128).asNumber(N.nbyte).next();
}
}
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 25ba75311e..e624af80f5 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -636,7 +636,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_injectX32768X_asNumberXN_nshortX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>(32768).AsNumber(N.Nshort)}},
{"g_injectX300X_asNumberXN_nbyteX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>(300).AsNumber(N.Nbyte)}},
{"g_injectX5X_asNumberXX", new List<Func<GraphTraversalSource,
IDictionary<string, object>, ITraversal>> {(g,p)
=>g.Inject<object>("5").AsNumber()}},
- {"g_injectX5X_asNumberXN_nintX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>("5").AsNumber(N.Nint)}},
+ {"g_injectX5X_asNumberXN_byteX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>("5").AsNumber(N.Nbyte)}},
{"g_injectX1_000X_asNumberXN_nintX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>("1,000").AsNumber(N.Nint)}},
{"g_injectXtestX_asNumberXX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>("test").AsNumber()}},
{"g_injectX_1__2__3__4_X_asNumberXX", new
List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>>
{(g,p) =>g.Inject<object>(new List<object> { 1, 2, 3, 4 }).AsNumber()}},
diff --git a/gremlin-go/driver/cucumber/gremlin.go
b/gremlin-go/driver/cucumber/gremlin.go
index 8612cdbf5e..be4614c490 100644
--- a/gremlin-go/driver/cucumber/gremlin.go
+++ b/gremlin-go/driver/cucumber/gremlin.go
@@ -606,7 +606,7 @@ var translationMap = map[string][]func(g
*gremlingo.GraphTraversalSource, p map[
"g_injectX32768X_asNumberXN_nshortX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.Inject(32768).AsNumber(gremlingo.N.Nshort)}},
"g_injectX300X_asNumberXN_nbyteX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.Inject(300).AsNumber(gremlingo.N.Nbyte)}},
"g_injectX5X_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.Inject("5").AsNumber()}},
- "g_injectX5X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.Inject("5").AsNumber(gremlingo.N.Nint)}},
+ "g_injectX5X_asNumberXN_byteX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.Inject("5").AsNumber(gremlingo.N.Nbyte)}},
"g_injectX1_000X_asNumberXN_nintX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return
g.Inject("1,000").AsNumber(gremlingo.N.Nint)}},
"g_injectXtestX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p
map[string]interface{}) *gremlingo.GraphTraversal {return
g.Inject("test").AsNumber()}},
"g_injectX_1__2__3__4_X_asNumberXX": {func(g
*gremlingo.GraphTraversalSource, p map[string]interface{})
*gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3,
4}).AsNumber()}},
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 5f75840cd2..eaf50c7474 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
@@ -637,7 +637,7 @@ const gremlins = {
g_injectX32768X_asNumberXN_nshortX: [function({g}) { return
g.inject(32768).asNumber(N.nshort) }],
g_injectX300X_asNumberXN_nbyteX: [function({g}) { return
g.inject(300).asNumber(N.nbyte) }],
g_injectX5X_asNumberXX: [function({g}) { return g.inject("5").asNumber()
}],
- g_injectX5X_asNumberXN_nintX: [function({g}) { return
g.inject("5").asNumber(N.nint) }],
+ g_injectX5X_asNumberXN_byteX: [function({g}) { return
g.inject("5").asNumber(N.nbyte) }],
g_injectX1_000X_asNumberXN_nintX: [function({g}) { return
g.inject("1,000").asNumber(N.nint) }],
g_injectXtestX_asNumberXX: [function({g}) { return
g.inject("test").asNumber() }],
g_injectX_1__2__3__4_X_asNumberXX: [function({g}) { return g.inject([1, 2,
3, 4]).asNumber() }],
diff --git a/gremlin-python/src/main/python/radish/gremlin.py
b/gremlin-python/src/main/python/radish/gremlin.py
index 8c2134f592..89731ece25 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -609,7 +609,7 @@ world.gremlins = {
'g_injectX32768X_asNumberXN_nshortX': [(lambda
g:g.inject(32768).as_number(N.nshort))],
'g_injectX300X_asNumberXN_nbyteX': [(lambda
g:g.inject(300).as_number(N.nbyte))],
'g_injectX5X_asNumberXX': [(lambda g:g.inject('5').as_number())],
- 'g_injectX5X_asNumberXN_nintX': [(lambda
g:g.inject('5').as_number(N.nint))],
+ 'g_injectX5X_asNumberXN_byteX': [(lambda
g:g.inject('5').as_number(N.nbyte))],
'g_injectX1_000X_asNumberXN_nintX': [(lambda
g:g.inject('1,000').as_number(N.nint))],
'g_injectXtestX_asNumberXX': [(lambda g:g.inject('test').as_number())],
'g_injectX_1__2__3__4_X_asNumberXX': [(lambda g:g.inject([1, 2, 3,
4]).as_number())],
diff --git
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature
index 868eb80274..2a5972a067 100644
---
a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature
+++
b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature
@@ -180,19 +180,19 @@ Feature: Step - asNumber()
When iterated to list
Then the result should be unordered
| result |
- | d[5].b |
+ | d[5].i |
@GraphComputerVerificationInjectionNotSupported
- Scenario: g_injectX5X_asNumberXN_nintX
+ Scenario: g_injectX5X_asNumberXN_byteX
Given the empty graph
And the traversal of
"""
- g.inject("5").asNumber(N.nint)
+ g.inject("5").asNumber(N.nbyte)
"""
When iterated to list
Then the result should be unordered
| result |
- | d[5].i |
+ | d[5].b |
@GraphComputerVerificationInjectionNotSupported
Scenario: g_injectX1_000X_asNumberXN_nintX