Skip to content

Commit 32dc384

Browse files
committed
Merge branch '3.8-dev'
2 parents 25f0e15 + ca58a97 commit 32dc384

File tree

65 files changed

+2209
-267
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+2209
-267
lines changed

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>.
154154
* Moved all lambda oriented Gremlin tests to `LambdaStepTest` in the Java test suite.
155155
* Removed the `@RemoteOnly` testing tag in Gherkin as lambda tests have all been moved to the Java test suite.
156156
* Updated gremlin-javascript to use GraphBinary as default instead of GraphSONv3
157+
* Added the `asNumber()` step to perform number conversion.
157158
* Renamed many types in the grammar for consistent use of terms "Literal", "Argument", and "Varargs"
158159
* Changed `gremlin-net` so that System.Text.Json is only listed as an explicit dependency when it is not available from the framework.
159160
* Fixed translation of numeric literals for Go losing type definitions

docs/src/dev/provider/gremlin-semantics.asciidoc

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -828,37 +828,33 @@ Incoming date remains unchanged.
828828
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsDateStep.java[source],
829829
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference]
830830
831-
[[asString-step]]
832-
=== asString()
831+
[[asNumber-step]]
832+
=== asNumber()
833833
834-
*Description:* Returns the value of incoming traverser as strings, or if `Scope.local` is specified, returns each element inside
835-
incoming list traverser as string.
834+
*Description:* 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.
836835
837-
*Syntax:* `asString()` | `asString(Scope scope)`
836+
*Syntax:* `asNumber()` | `asNumber(N numberToken)`
838837
839838
[width="100%",options="header"]
840839
|=========================================================
841840
|Start Step |Mid Step |Modulated |Domain |Range
842-
|N |Y |N |`any` |`String`/`List`
841+
|N |Y |N |`Number`/`String` |`Number`
843842
|=========================================================
844843
845844
*Arguments:*
846845
847-
* `scope` - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers.
848-
The `global` scope will operate on individual traverser, casting all (except `null`) to string. The `local` scope will behave like
849-
`global` for everything except lists, where it will cast individual non-`null` elements inside the list into string and return a
850-
list of string instead.
846+
* `numberToken` - The enum `N` to denote the desired type to parse/cast to.
851847
852-
Null values from the incoming traverser are not processed and remain as null when returned.
848+
If no type token is provided, the incoming number remains unchanged.
853849
854850
*Exceptions*
855851
856-
None
857-
858-
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringGlobalStep.java[source],
859-
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringLocalStep.java[source (local)],
860-
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
852+
* If any overflow occurs during narrowing of types, then an `ArithmeticException` will be thrown.
853+
* If the incoming string cannot be parsed into a valid number format, then a `NumberFormatException` will be thrown.
854+
* If the incoming traverser is a non-String/Number (including `null`) value then an `IllegalArgumentException` will be thrown.
861855
856+
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java[source],
857+
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asNumber-step[reference]
862858
863859
[[barrier-step]]
864860
=== barrier()
@@ -1063,6 +1059,36 @@ None
10631059
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/SideEffectCapStep.java[source],
10641060
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#cap-step[reference]
10651061
1062+
[[asString-step]]
1063+
=== asString()
1064+
1065+
*Description:* Returns the value of incoming traverser as strings, or if `Scope.local` is specified, returns each element inside
1066+
incoming list traverser as string.
1067+
1068+
*Syntax:* `asString()` | `asString(Scope scope)`
1069+
1070+
[width="100%",options="header"]
1071+
|=========================================================
1072+
|Start Step |Mid Step |Modulated |Domain |Range
1073+
|N |Y |N |`any` |`String`/`List`
1074+
|=========================================================
1075+
1076+
*Arguments:*
1077+
1078+
* `scope` - Determines the type of traverser it operates on. Both scopes will operate on the level of individual traversers.
1079+
The `global` scope will operate on individual traverser, casting all (except `null`) to string. The `local` scope will behave like
1080+
`global` for everything except lists, where it will cast individual non-`null` elements inside the list into string and return a
1081+
list of string instead.
1082+
1083+
Null values from the incoming traverser are not processed and remain as null when returned.
1084+
1085+
*Exceptions*
1086+
None
1087+
1088+
See: link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringGlobalStep.java[source],
1089+
link:https://github.com/apache/tinkerpop/tree/x.y.z/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsStringLocalStep.java[source (local)],
1090+
link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asString-step[reference]
1091+
10661092
[[call-step]]
10671093
=== call()
10681094

docs/src/reference/the-traversal.asciidoc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,52 @@ g.inject(datetime("2023-08-24T00:00:00Z")).asDate() <3>
854854
855855
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asDate()++[`asDate()`]
856856
857+
[[asNumber-step]]
858+
=== AsNumber Step
859+
860+
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.
861+
862+
Numerical input will pass through unless a type is specified by the number token. `ArithmeticException` will be thrown for any overflow during narrowing of types.
863+
864+
String inputs are parsed into numeric values. By default, the value will be parsed as an integer if it represents a whole number, or as a double if it contains a decimal point. A `NumberFormatException` will be thrown if the string cannot be parsed into a valid number format.
865+
866+
All other input types will result in `IllegalArgumentException`.
867+
868+
[gremlin-groovy,modern]
869+
----
870+
g.inject(1234).asNumber() <1>
871+
g.inject(1.76).asNumber() <2>
872+
g.inject(1.76).asNumber(N.int_) <3>
873+
g.inject("1b").asNumber() <4>
874+
g.inject(33550336).asNumber(N.byte_) <5>
875+
----
876+
877+
<1> An int will be passed through.
878+
<2> A double will be passed through.
879+
<3> A double is converted into an int.
880+
<4> String containing any character other than numerical ones will result in `NumberFormatException`.
881+
<5> Narrowing of int to byte that overflows will throw `ArithmeticException`.
882+
883+
[NOTE, caption=Java]
884+
====
885+
The enums values `byte`, `short`, `int`, `long`, `float`, `double` are reserved word in Java, and therefore must be referred to in Gremlin with an underscore appended as a suffix: `byte_`, `short_`, `int_`, `long_`, `float_`, `double_`.
886+
====
887+
888+
[NOTE, caption=Groovy & Gremlin Console]
889+
====
890+
The enums values `byte`, `short`, `int`, `long`, `float`, `double` are reserved word in Groovy, therefore as the Gremlin Console is Groovy-based, they must be referred to in Gremlin with an underscore appended as a suffix: `byte_`, `short_`, `int_`, `long_`, `float_`, `double_`.
891+
====
892+
893+
[NOTE, caption=JavaScript]
894+
====
895+
The enums values `byte`, `short`, `int`, `long`, `float`, `double` are reserved word in Javascript, and therefore must be referred to in Gremlin with an underscore appended as a suffix: `byte_`, `short_`, `int_`, `long_`, `float_`, `double_`.
896+
====
897+
898+
*Additional References*
899+
900+
link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asNumber()++[`asNumber()`]
901+
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)`]
902+
857903
[[barrier-step]]
858904
=== Barrier Step
859905

docs/src/upgrade/release-3.8.x.asciidoc

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,69 @@ complete list of all the modifications that are part of this release.
3030
3131
=== Upgrading for Users
3232
33+
==== Number Conversion Step
34+
35+
We have been iteratively introducing new language features into Gremlin, with the most recent major additions being string, list and date manipulation
36+
steps introduced in the 3.7 line. In 3.8.0, we are now introducing a number conversion step, `asNumber()`, to bridge
37+
a gap in casting functionalities.
38+
39+
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`).
40+
41+
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:
42+
43+
[source,text]
44+
----
45+
// sum() step can only take numbers
46+
gremlin> g.inject(1.0, 2l, 3, "4", "0x5").sum()
47+
class java.lang.String cannot be cast to class java.lang.Number
48+
49+
// use asNumber() to avoid casting exceptions
50+
gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum()
51+
==>15.0
52+
53+
// given sum() step returned a double, one can use asNumber() to further cast the result into desired type
54+
gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum().asNumber(N.int_)
55+
==>15
56+
----
57+
58+
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.
59+
60+
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:
61+
62+
[source,text]
63+
----
64+
gremlin> g.inject(5.0).asNumber(N.int_)
65+
==> 5 // casts double to int
66+
gremlin> g.inject(12).asNumber(N.byte_)
67+
==> 12
68+
gremlin> g.inject(128).asNumber(N.byte_)
69+
==> ArithmeticException
70+
----
71+
72+
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:
73+
74+
[source,text]
75+
----
76+
gremlin> g.inject("5").asNumber()
77+
==> 5
78+
gremlin> g.inject("5.7").asNumber(N.int_)
79+
==> 5
80+
gremlin> g.inject("1,000").asNumber(N.int_)
81+
==> NumberFormatException
82+
gremlin> g.inject("128").asNumber(N.byte_)
83+
==> ArithmeticException
84+
----
85+
86+
All other input types will result in `IllegalArgumentException`:
87+
[source,text]
88+
----
89+
gremlin> g.inject([1, 2, 3, 4]).asNumber()
90+
==> IllegalArgumentException
91+
----
92+
93+
See: link:https://tinkerpop.apache.org/docs/3.8.0/reference/#asNumber-step[asNumber()-step]
94+
See: link:https://issues.apache.org/jira/browse/TINKERPOP-3166[TINKERPOP-3166]
95+
3396
==== Boolean Conversion Step
3497
3598
The `asBool()` step bridges another gap in Gremlin's casting functionalities. Users now have the ability to parse strings and

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
import org.apache.tinkerpop.gremlin.process.traversal.DT;
6262
import org.apache.tinkerpop.gremlin.process.traversal.IO;
6363
import org.apache.tinkerpop.gremlin.process.traversal.Merge;
64+
import org.apache.tinkerpop.gremlin.process.traversal.N;
6465
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
6566
import org.apache.tinkerpop.gremlin.process.traversal.Order;
6667
import org.apache.tinkerpop.gremlin.process.traversal.P;
@@ -202,6 +203,7 @@ public final class CoreImports {
202203
CLASS_IMPORTS.add(Direction.class);
203204
CLASS_IMPORTS.add(DT.class);
204205
CLASS_IMPORTS.add(Merge.class);
206+
CLASS_IMPORTS.add(N.class);
205207
CLASS_IMPORTS.add(Operator.class);
206208
CLASS_IMPORTS.add(Order.class);
207209
CLASS_IMPORTS.add(Pop.class);
@@ -358,6 +360,7 @@ public final class CoreImports {
358360
Collections.addAll(ENUM_IMPORTS, Direction.values());
359361
Collections.addAll(ENUM_IMPORTS, DT.values());
360362
Collections.addAll(ENUM_IMPORTS, Merge.values());
363+
Collections.addAll(ENUM_IMPORTS, N.values());
361364
Collections.addAll(ENUM_IMPORTS, Operator.values());
362365
Collections.addAll(ENUM_IMPORTS, Order.values());
363366
Collections.addAll(ENUM_IMPORTS, Pop.values());

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,14 @@ public T visitTraversalMethod_from_Traversal(GremlinParser.TraversalMethod_from_
10821082
* {@inheritDoc}
10831083
*/
10841084
@Override public T visitTraversalMethod_dateDiff_Date(final GremlinParser.TraversalMethod_dateDiff_DateContext ctx) { notImplemented(ctx); return null; }
1085+
/**
1086+
* {@inheritDoc}
1087+
*/
1088+
@Override public T visitTraversalMethod_asNumber_Empty(final GremlinParser.TraversalMethod_asNumber_EmptyContext ctx) { notImplemented(ctx); return null; }
1089+
/**
1090+
* {@inheritDoc}
1091+
*/
1092+
@Override public T visitTraversalMethod_asNumber_traversalN(final GremlinParser.TraversalMethod_asNumber_traversalNContext ctx) { notImplemented(ctx); return null; }
10851093
/**
10861094
* {@inheritDoc}
10871095
*/
@@ -1122,6 +1130,10 @@ public T visitTraversalMethod_from_Traversal(GremlinParser.TraversalMethod_from_
11221130
* {@inheritDoc}
11231131
*/
11241132
@Override public T visitTraversalDT(GremlinParser.TraversalDTContext ctx) { notImplemented(ctx); return null; }
1133+
/**
1134+
* {@inheritDoc}
1135+
*/
1136+
@Override public T visitTraversalN(GremlinParser.TraversalNContext ctx) { notImplemented(ctx); return null; }
11251137
/**
11261138
* {@inheritDoc}
11271139
*/

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,11 @@ public Object visitTraversalPick(final GremlinParser.TraversalPickContext ctx) {
601601
return TraversalEnumParser.parseTraversalEnumFromContext(Pick.class, ctx);
602602
}
603603

604+
@Override
605+
public Object visitTraversalN(final GremlinParser.TraversalNContext ctx) {
606+
return TraversalEnumParser.parseTraversalNFromContext(ctx);
607+
}
608+
604609
@Override
605610
public Object visitTraversalStrategy(final GremlinParser.TraversalStrategyContext ctx) {
606611
return antlr.traversalStrategyVisitor.visitTraversalStrategy(ctx);

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParser.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.tinkerpop.gremlin.language.grammar;
2020

2121
import org.antlr.v4.runtime.tree.ParseTree;
22+
import org.apache.tinkerpop.gremlin.process.traversal.N;
2223
import org.apache.tinkerpop.gremlin.process.traversal.Scope;
2324
import org.apache.tinkerpop.gremlin.structure.Column;
2425
import org.apache.tinkerpop.gremlin.structure.Direction;
@@ -79,4 +80,14 @@ public static Function parseTraversalFunctionFromContext(final GremlinParser.Tra
7980
throw new GremlinParserException("Unrecognized enum for traversal function");
8081
}
8182
}
83+
84+
/**
85+
* Parsing of {@link N} requires some special handling because of java keyword collision.
86+
*/
87+
public static N parseTraversalNFromContext(final GremlinParser.TraversalNContext context) {
88+
String text = context.getText();
89+
if (text.startsWith(N.class.getSimpleName()))
90+
text = text.substring(N.class.getSimpleName().length() + 1);
91+
return text.startsWith("big") ? N.valueOf(text) : N.valueOf(text + "_");
92+
}
8293
}

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.apache.tinkerpop.gremlin.process.traversal.DT;
2222
import org.apache.tinkerpop.gremlin.process.traversal.Merge;
23+
import org.apache.tinkerpop.gremlin.process.traversal.N;
2324
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
2425
import org.apache.tinkerpop.gremlin.process.traversal.Order;
2526
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -2274,6 +2275,22 @@ public GraphTraversal visitTraversalMethod_dateDiff_Date(final GremlinParser.Tra
22742275
return graphTraversal.dateDiff(antlr.genericVisitor.parseDate(ctx.dateLiteral()));
22752276
}
22762277

2278+
/**
2279+
* {@inheritDoc}
2280+
*/
2281+
@Override
2282+
public GraphTraversal visitTraversalMethod_asNumber_Empty(final GremlinParser.TraversalMethod_asNumber_EmptyContext ctx) {
2283+
return graphTraversal.asNumber();
2284+
}
2285+
2286+
/**
2287+
* {@inheritDoc}
2288+
*/
2289+
@Override
2290+
public GraphTraversal visitTraversalMethod_asNumber_traversalN(final GremlinParser.TraversalMethod_asNumber_traversalNContext ctx) {
2291+
return graphTraversal.asNumber(
2292+
TraversalEnumParser.parseTraversalNFromContext(ctx.traversalN()));
2293+
}
22772294

22782295
public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) {
22792296
return ctx.nestedTraversalExpr().nestedTraversal()

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ public GroovyTranslateVisitor(final String graphTraversalSourceName) {
4646
super(graphTraversalSourceName);
4747
}
4848

49+
@Override
50+
public Void visitTraversalN(GremlinParser.TraversalNContext ctx) {
51+
final String[] split = ctx.getText().split("\\.");
52+
sb.append(processGremlinSymbol(split[0])).append(".");
53+
sb.append(processGremlinSymbol(split[1]));
54+
if (!split[1].startsWith("big")) sb.append("_");
55+
return null;
56+
}
57+
4958
@Override
5059
public Void visitIntegerLiteral(final GremlinParser.IntegerLiteralContext ctx) {
5160
final String integerLiteral = ctx.getText().toLowerCase();

0 commit comments

Comments
 (0)