From e4ec14140cd978fd903c4317b24951521258fdea Mon Sep 17 00:00:00 2001 From: Peter Tribe Date: Thu, 12 Jun 2025 18:42:11 -0600 Subject: [PATCH 1/9] asNumber implementations --- .../tinkerpop/gremlin/jsr223/CoreImports.java | 3 + .../grammar/DefaultGremlinBaseVisitor.java | 12 + .../grammar/TraversalMethodVisitor.java | 17 ++ .../gremlin/process/traversal/N.java | 36 +++ .../traversal/dsl/graph/GraphTraversal.java | 27 ++ .../process/traversal/dsl/graph/__.java | 15 + .../traversal/step/map/AsNumberStep.java | 186 ++++++++++++ .../traversal/util/BytecodeHelper.java | 2 + .../gremlin/structure/io/binary/DataType.java | 1 + .../io/binary/TypeSerializerRegistry.java | 2 + .../io/binary/types/EnumSerializer.java | 2 + .../structure/io/graphson/GraphSONModule.java | 7 + .../structure/io/gryo/GryoVersion.java | 2 + .../Process/Traversal/GraphTraversal.cs | 9 + .../src/Gremlin.Net/Process/Traversal/N.cs | 74 +++++ .../src/Gremlin.Net/Process/Traversal/__.cs | 8 + .../Structure/IO/GraphBinary/DataType.cs | 1 + .../IO/GraphBinary/TypeSerializerRegistry.cs | 2 + .../IO/GraphBinary/Types/EnumSerializer.cs | 6 + .../Structure/IO/GraphSON/GraphSONReader.cs | 1 + .../Structure/IO/GraphSON/NDeserializer.cs | 38 +++ .../Gherkin/Gremlin.cs | 1 + .../IO/GraphBinary/GraphBinaryTests.cs | 15 + .../IO/GraphSON/GraphSONReaderTests.cs | 12 + .../IO/GraphSON/GraphSONWriterTests.cs | 11 + gremlin-go/driver/anonymousTraversal.go | 5 + gremlin-go/driver/graphBinary.go | 3 + gremlin-go/driver/graphTraversal.go | 6 + gremlin-go/driver/serializer.go | 2 + gremlin-go/driver/traversal.go | 34 +++ gremlin-javascript/build/generate.groovy | 1 + .../lib/process/graph-traversal.js | 10 + .../lib/process/traversal.js | 1 + .../structure/io/binary/internals/DataType.js | 1 + .../io/binary/internals/EnumSerializer.js | 1 + .../test/cucumber/gremlin.js | 21 ++ gremlin-language/src/main/antlr4/Gremlin.g4 | 38 +++ .../test/features/map/AsNumber.feature | 275 ++++++++++++++++++ 38 files changed, 888 insertions(+) create mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java create mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java create mode 100644 gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs create mode 100644 gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/NDeserializer.cs create mode 100644 gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java index a4d77b0fd2b..82e3ed78c47 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/CoreImports.java @@ -60,6 +60,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.IO; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -204,6 +205,7 @@ public final class CoreImports { CLASS_IMPORTS.add(Direction.class); CLASS_IMPORTS.add(DT.class); CLASS_IMPORTS.add(Merge.class); + CLASS_IMPORTS.add(N.class); CLASS_IMPORTS.add(Operator.class); CLASS_IMPORTS.add(Order.class); CLASS_IMPORTS.add(Pop.class); @@ -363,6 +365,7 @@ public final class CoreImports { Collections.addAll(ENUM_IMPORTS, Direction.values()); Collections.addAll(ENUM_IMPORTS, DT.values()); Collections.addAll(ENUM_IMPORTS, Merge.values()); + Collections.addAll(ENUM_IMPORTS, N.values()); Collections.addAll(ENUM_IMPORTS, Operator.values()); Collections.addAll(ENUM_IMPORTS, Order.values()); Collections.addAll(ENUM_IMPORTS, Pop.values()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java index cf3946e5506..e37503226b0 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/DefaultGremlinBaseVisitor.java @@ -1067,6 +1067,14 @@ protected void notImplemented(final ParseTree ctx) { * {@inheritDoc} */ @Override public T visitTraversalMethod_dateDiff_Date(final GremlinParser.TraversalMethod_dateDiff_DateContext ctx) { notImplemented(ctx); return null; } + /** + * {@inheritDoc} + */ + @Override public T visitTraversalMethod_asNumber_Empty(final GremlinParser.TraversalMethod_asNumber_EmptyContext ctx) { notImplemented(ctx); return null; } + /** + * {@inheritDoc} + */ + @Override public T visitTraversalMethod_asNumber_traversalN(final GremlinParser.TraversalMethod_asNumber_traversalNContext ctx) { notImplemented(ctx); return null; } /** * {@inheritDoc} */ @@ -1107,6 +1115,10 @@ protected void notImplemented(final ParseTree ctx) { * {@inheritDoc} */ @Override public T visitTraversalDT(GremlinParser.TraversalDTContext ctx) { notImplemented(ctx); return null; } + /** + * {@inheritDoc} + */ + @Override public T visitTraversalN(GremlinParser.TraversalNContext ctx) { notImplemented(ctx); return null; } /** * {@inheritDoc} */ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java index 6a45f660ffd..ccc24cea14a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java @@ -20,6 +20,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Pop; @@ -2102,6 +2103,22 @@ public GraphTraversal visitTraversalMethod_dateDiff_Date(final GremlinParser.Tra return graphTraversal.dateDiff(antlr.genericVisitor.parseDate(ctx.dateLiteral())); } + /** + * {@inheritDoc} + */ + @Override + public GraphTraversal visitTraversalMethod_asNumber_Empty(final GremlinParser.TraversalMethod_asNumber_EmptyContext ctx) { + return graphTraversal.asNumber(); + } + + /** + * {@inheritDoc} + */ + @Override + public GraphTraversal visitTraversalMethod_asNumber_traversalN(final GremlinParser.TraversalMethod_asNumber_traversalNContext ctx) { + return graphTraversal.asNumber( + TraversalEnumParser.parseTraversalEnumFromContext(N.class, ctx.traversalN())); + } public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) { return ctx.nestedTraversalExpr().nestedTraversal() diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java new file mode 100644 index 00000000000..d0bf66bfdc5 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal; + +import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsNumberStep; + +/** + * Tokens that are used to denote different units of number. + * Used with {@link AsNumberStep} step. + */ +public enum N { + nbyte, + nshort, + nint, + nlong, + nfloat, + ndouble, + nbigInt, + nbigDecimal +} \ No newline at end of file diff --git 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 index 32b830c2521..e1daab30d29 100644 --- 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 @@ -28,6 +28,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Failure; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Path; @@ -83,6 +84,7 @@ 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.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; @@ -1903,6 +1905,30 @@ public default GraphTraversal dateDiff(final Traversal dateTraver return this.asAdmin().addStep(new DateDiffStep<>(this.asAdmin(), dateTraversal)); } + /** + * Parse value of the incoming traverser as an ISO-8601 {@link Number}. + * + * @return the traversal with an appended {@link AsNumberStep}. + * @see Reference Documentation - AsNumberStep Step + * @since 3.7.1 + */ + public default GraphTraversal asNumber() { + this.asAdmin().getBytecode().addStep(Symbols.asNumber); + return this.asAdmin().addStep(new AsNumberStep<>(this.asAdmin())); + } + + /** + * Parse value of the incoming traverser as an ISO-8601 {@link Number}. + * + * @return the traversal with an appended {@link AsNumberStep}. + * @see Reference Documentation - AsNumberStep Step + * @since 3.7.1 + */ + public default GraphTraversal asNumber(final N numberToken) { + this.asAdmin().getBytecode().addStep(Symbols.asNumber, numberToken); + return this.asAdmin().addStep(new AsNumberStep<>(this.asAdmin(), numberToken)); + } + /** * Calculates the difference between the list traverser and list argument. * @@ -4122,6 +4148,7 @@ private Symbols() { public static final String asDate = "asDate"; public static final String dateAdd = "dateAdd"; public static final String dateDiff = "dateDiff"; + public static final String asNumber = "asNumber"; public static final String all = "all"; public static final String any = "any"; public static final String merge = "merge"; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java index e1edcfc5f4f..e3250f30951 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.dsl.graph; import org.apache.tinkerpop.gremlin.process.traversal.DT; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.Pop; @@ -809,6 +810,20 @@ public static GraphTraversal dateDiff(final Traversalstart().dateDiff(dateTraversal); } + /** + * @see GraphTraversal#asNumber() + */ + public static GraphTraversal asNumber() { + return __.start().asNumber(); + } + + /** + * @see GraphTraversal#asNumber(N) + */ + public static GraphTraversal asNumber(final N numberToken) { + return __.start().asNumber(numberToken); + } + /** * @see GraphTraversal#difference(Object) */ 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 new file mode 100644 index 00000000000..4c1bfe4e9f2 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStep.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.tinkerpop.gremlin.process.traversal.N; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; +import org.apache.tinkerpop.gremlin.structure.util.StringFactory; +import org.apache.tinkerpop.gremlin.util.NumberHelper; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Reference implementation for date concatenation step. + * + * @author Valentyn Kahamlyk + */ +public final class AsNumberStep extends ScalarMapStep { + + private final N numberToken; + private final boolean auto; + + public AsNumberStep(final Traversal.Admin traversal) { + super(traversal); + this.numberToken = N.nlong; + this.auto = true; + } + + public AsNumberStep(final Traversal.Admin traversal, final N numberToken) { + super(traversal); + this.numberToken = numberToken; + this.auto = false; + } + + private static int getNumberBitsBasedOnValue(Number number) { + final Class clazz = number.getClass(); + if (clazz.equals(BigInteger.class)) { + return 128; + } else if (clazz.equals(BigDecimal.class)) { + return 128; + } + boolean floatingPoint = (clazz.equals(Float.class) || clazz.equals(Double.class)); + if (!floatingPoint && (number.longValue() >= Byte.MIN_VALUE) && (number.longValue() <= Byte.MAX_VALUE)) { + return 8; + } else if (!floatingPoint && (number.longValue() >= Short.MIN_VALUE) && (number.longValue() <= Short.MAX_VALUE)) { + return 16; + } else if (!floatingPoint && (number.longValue() >= Integer.MIN_VALUE) && (number.longValue() <= Integer.MAX_VALUE)) { + return 32; + } else if (floatingPoint && (number.doubleValue() >= Float.MIN_VALUE) && (number.doubleValue() <= Float.MAX_VALUE)) { + return 32; + } else { + return 64; + } + } + + private static int getNumberTokenBits(N numberToken) { + if (numberToken == N.nbyte) { + return 8; + } else if (numberToken == N.nshort) { + return 16; + } else if (numberToken == N.nint) { + return 32; + } else if (numberToken == N.nlong) { + return 64; + } else if (numberToken == N.nfloat) { + return 32; + } else if (numberToken == N.ndouble) { + return 64; + } + return 128; + } + + private static Number castNumber(Number number, N numberToken) { + int sourceBits = getNumberBitsBasedOnValue(number); + int targetBits = getNumberTokenBits(numberToken); + if (sourceBits > targetBits) { + throw new ArithmeticException("Can not convert number type as would cause overflow."); + } + if (numberToken == N.nbyte) { + return number.byteValue(); + } else if (numberToken == N.nshort) { + return number.shortValue(); + } else if (numberToken == N.nint) { + return number.intValue(); + } else if (numberToken == N.nlong) { + return number.longValue(); + } else if (numberToken == N.nfloat) { + return number.floatValue(); + } else if (numberToken == N.ndouble) { + return number.doubleValue(); + } else if (numberToken == N.nbigInt) { + return BigInteger.valueOf(number.longValue()); + } else if (numberToken == N.nbigDecimal) { + return BigDecimal.valueOf(number.doubleValue()); + } + return number; + } + + private static Number autoNumber(Number number) { + final Class clazz = number.getClass(); + if (clazz.equals(Float.class)) { + return castNumber(number, N.ndouble); + } + return number; + } + + public static Number parseNumber(final String value) { + try { + boolean isFloatingPoint = value.contains(".") || value.contains("e") || value.contains("E"); + if (isFloatingPoint) { + BigDecimal result = new BigDecimal(value.trim()); + if (BigDecimal.valueOf(result.doubleValue()).compareTo(result) == 0) { + return result.doubleValue(); + } + 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) { + return result.intValue(); + } else if (result.bitLength() <= 63) { + return result.longValue(); + } + return result; + } catch (NumberFormatException nfe) { + throw new NumberFormatException(String.format("Can not parse number: '%s'", value)); + } + } + + @Override + protected Number map(Traverser.Admin traverser) { + final Object object = traverser.get(); + if (object instanceof String) { + String numberText = (String) object; + Number number = parseNumber(numberText); + return auto ? autoNumber(number) : castNumber(number, numberToken); + } else if (object instanceof Number) { + Number number = (Number) object; + return auto ? autoNumber(number) : castNumber(number, numberToken); + } + throw new IllegalArgumentException(String.format("Can't parse '%s' as number.", object == null ? "null" : object.getClass().getSimpleName())); + } + + @Override + public Set getRequirements() { + return Collections.singleton(TraverserRequirement.OBJECT); + } + + @Override + public void setTraversal(final Traversal.Admin parentTraversal) { + super.setTraversal(parentTraversal); + } + + @Override + public String toString() { + return StringFactory.stepString(this); + } +} diff --git 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 index 9a508be6d52..e9e2ac90daa 100644 --- 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 @@ -65,6 +65,7 @@ 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.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; @@ -247,6 +248,7 @@ public final class BytecodeHelper { put(GraphTraversal.Symbols.asDate, Collections.singletonList(AsDateStep.class)); put(GraphTraversal.Symbols.dateAdd, Collections.singletonList(DateAddStep.class)); put(GraphTraversal.Symbols.dateDiff, Collections.singletonList(DateDiffStep.class)); + put(GraphTraversal.Symbols.asNumber, Collections.singletonList(AsNumberStep.class)); put(GraphTraversal.Symbols.all, Collections.singletonList(AllStep.class)); put(GraphTraversal.Symbols.any, Collections.singletonList(AnyStep.class)); put(GraphTraversal.Symbols.combine, Collections.singletonList(CombineStep.class)); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java index e3fd09f16ab..c0738a34962 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/DataType.java @@ -72,6 +72,7 @@ public enum DataType { TRAVERSALMETRICS(0x2D), MERGE(0x2E), DT(0x2F), + N(0x30), CHAR(0X80), DURATION(0X81), diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java index 99581793188..4b48d8d2112 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -166,6 +167,7 @@ public static Builder build() { new RegistryEntry<>(Direction.class, EnumSerializer.DirectionSerializer), new RegistryEntry<>(DT.class, EnumSerializer.DTSerializer), new RegistryEntry<>(Merge.class, EnumSerializer.MergeSerializer), + new RegistryEntry<>(N.class, EnumSerializer.NSerializer), new RegistryEntry<>(Operator.class, EnumSerializer.OperatorSerializer), new RegistryEntry<>(Order.class, EnumSerializer.OrderSerializer), new RegistryEntry<>(Pick.class, EnumSerializer.PickSerializer), diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java index dcd14694605..730ffab1c92 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java @@ -20,6 +20,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.Pick; @@ -51,6 +52,7 @@ public class EnumSerializer extends SimpleTypeSerializer { public static final EnumSerializer DirectionSerializer = new EnumSerializer<>(DataType.DIRECTION, Direction::valueOf); public static final EnumSerializer
DTSerializer = new EnumSerializer<>(DataType.DT, DT::valueOf); public static final EnumSerializer MergeSerializer = new EnumSerializer<>(DataType.MERGE, Merge::valueOf); + public static final EnumSerializer NSerializer = new EnumSerializer<>(DataType.N, N::valueOf); public static final EnumSerializer OperatorSerializer = new EnumSerializer<>(DataType.OPERATOR, Operator::valueOf); public static final EnumSerializer OrderSerializer = new EnumSerializer<>(DataType.ORDER, Order::valueOf); public static final EnumSerializer PickSerializer = new EnumSerializer<>(DataType.PICK, Pick::valueOf); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java index 23cd17cfac2..1869f3d3b1a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java @@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -185,6 +186,7 @@ static final class GraphSONModuleV3 extends GraphSONModule { Direction.class, DT.class, Merge.class, + N.class, Operator.class, Order.class, Pop.class, @@ -277,6 +279,7 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Direction.class, DT.class, Merge.class, + N.class, Operator.class, Order.class, Pop.class, @@ -324,6 +327,7 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Direction.values(), DT.values(), Merge.values(), + N.values(), Operator.values(), Order.values(), Pop.values(), @@ -445,6 +449,7 @@ static final class GraphSONModuleV2 extends GraphSONModule { Direction.class, DT.class, Merge.class, + N.class, Operator.class, Order.class, Pop.class, @@ -531,6 +536,7 @@ protected GraphSONModuleV2(final boolean normalize) { Direction.class, DT.class, Merge.class, + N.class, Operator.class, Order.class, Pop.class, @@ -570,6 +576,7 @@ protected GraphSONModuleV2(final boolean normalize) { Direction.values(), DT.values(), Merge.values(), + N.values(), Operator.values(), Order.values(), Pop.values(), diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java index f4eb044b0b1..65b0f7164c9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java @@ -342,6 +342,7 @@ public static List> initV3Registrations() { add(GryoTypeReg.of(Pick.class, 137)); add(GryoTypeReg.of(DT.class, 198)); add(GryoTypeReg.of(Merge.class, 196)); + add(GryoTypeReg.of(DT.class, 199)); add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer())); add(GryoTypeReg.of(MultiComparator.class, 165)); @@ -550,6 +551,7 @@ public static List> initV1Registrations() { add(GryoTypeReg.of(Pick.class, 137)); add(GryoTypeReg.of(DT.class, 198)); add(GryoTypeReg.of(Merge.class, 196)); + add(GryoTypeReg.of(DT.class, 199)); add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer())); add(GryoTypeReg.of(MultiComparator.class, 165)); diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs index 8adff6b8193..07825fe7c7c 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs @@ -670,6 +670,15 @@ public GraphTraversal DateDiff(ITraversal dateTraversal) return Wrap(this); } + /// + /// Adds the asNumber step to this . + /// + public GraphTraversal AsNumber(N numberToken) + { + Bytecode.AddStep("asNumber", numberToken); + return Wrap(this); + } + /// /// Adds the dedup step to this . /// diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs new file mode 100644 index 00000000000..8996957b30c --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs @@ -0,0 +1,74 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using System; +using System.Collections.Generic; + +namespace Gremlin.Net.Process.Traversal +{ +#pragma warning disable 1591 + + public class N : EnumWrapper, IFunction + { + private N(string enumValue) + : base("N", enumValue) + { + } + + public static N NByte => new N("nbyte"); + public static N NShort => new N("nshort"); + public static N NInt => new N("nint"); + public static N NLong => new N("nlong"); + public static N NFloat => new N("nfloat"); + public static N NDouble => new N("ndouble"); + public static N NBigInt => new N("nbigInt"); + public static N NBigDecimal => new N("nbigDecimal"); + + private static readonly Dictionary Properties = new() + { + { "nbyte", NByte }, + { "nshort", NShort }, + { "nint", NInt }, + { "nlong", NLong }, + { "nfloat", NFloat }, + { "ndouble", NDouble }, + { "nbigInt", NBigInt }, + { "nbigDecimal", NBigDecimal }, + }; + + /// + /// Gets the Merge enumeration by value. + /// + public static N GetByValue(string value) + { + if (!Properties.TryGetValue(value, out var property)) + { + throw new ArgumentException($"No matching N for value '{value}'"); + } + return property; + } + } + + +#pragma warning restore 1591 +} \ No newline at end of file diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs index 1e1995caadd..d21cba4911c 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs @@ -442,6 +442,14 @@ public static GraphTraversal DateDiff(ITraversal dateTraversal) return new GraphTraversal().DateDiff(dateTraversal); } + /// + /// Spawns a and adds the dateAdd step to that traversal. + /// + public static GraphTraversal AsNumber(N numberToken) + { + return new GraphTraversal().AsNumber(numberToken); + } + /// /// Spawns a and adds the dedup step to that traversal. /// diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs index 161db1b1af9..9cae6ddfe2f 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/DataType.cs @@ -57,6 +57,7 @@ public class DataType : IEquatable public static readonly DataType Direction = new DataType(0x18); public static readonly DataType DT = new DataType(0x2F); public static readonly DataType Merge = new DataType(0x2E); + public static readonly DataType N = new DataType(0x30); public static readonly DataType Operator = new DataType(0x19); public static readonly DataType Order = new DataType(0x1A); public static readonly DataType Pick = new DataType(0x1B); diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs index 89a6c97e561..74e00001e0e 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/TypeSerializerRegistry.cs @@ -64,6 +64,7 @@ public class TypeSerializerRegistry {typeof(Direction), EnumSerializers.DirectionSerializer}, {typeof(DT), EnumSerializers.DTSerializer}, {typeof(Merge), EnumSerializers.MergeSerializer}, + {typeof(N), EnumSerializers.NSerializer}, {typeof(Operator), EnumSerializers.OperatorSerializer}, {typeof(Order), EnumSerializers.OrderSerializer}, {typeof(Pick), EnumSerializers.PickSerializer}, @@ -113,6 +114,7 @@ public class TypeSerializerRegistry {DataType.Direction, EnumSerializers.DirectionSerializer}, {DataType.DT, EnumSerializers.DTSerializer}, {DataType.Merge, EnumSerializers.MergeSerializer}, + {DataType.N, EnumSerializers.NSerializer}, {DataType.Operator, EnumSerializers.OperatorSerializer}, {DataType.Order, EnumSerializers.OrderSerializer}, {DataType.Pick, EnumSerializers.PickSerializer}, diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs index a3ea2f85353..d50232c6d68 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphBinary/Types/EnumSerializer.cs @@ -71,6 +71,12 @@ public static class EnumSerializers public static readonly EnumSerializer MergeSerializer = new EnumSerializer(DataType.Merge, Merge.GetByValue); + /// + /// A serializer for values. + /// + public static readonly EnumSerializer NSerializer = + new EnumSerializer(DataType.N, N.GetByValue); + /// /// A serializer for values. /// diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs index 779c5594a1d..52e26992a6e 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/GraphSONReader.cs @@ -47,6 +47,7 @@ public abstract class GraphSONReader { "g:Direction", new DirectionDeserializer() }, { "g:DT", new DTDeserializer() }, { "g:Merge", new MergeDeserializer() }, + { "g:N", new NDeserializer() }, { "g:UUID", new UuidDeserializer() }, { "g:Date", new DateDeserializer() }, { "g:Timestamp", new DateDeserializer() }, diff --git a/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/NDeserializer.cs b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/NDeserializer.cs new file mode 100644 index 00000000000..4e5c4334954 --- /dev/null +++ b/gremlin-dotnet/src/Gremlin.Net/Structure/IO/GraphSON/NDeserializer.cs @@ -0,0 +1,38 @@ +#region License + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#endregion + +using System.IO; +using System.Text.Json; +using Gremlin.Net.Process.Traversal; + +namespace Gremlin.Net.Structure.IO.GraphSON +{ + internal class NDeserializer : IGraphSONDeserializer + { + public dynamic Objectify(JsonElement graphsonObject, GraphSONReader reader) + { + return N.GetByValue(graphsonObject.GetString() ?? + throw new IOException($"Read null but expected a {nameof(N)} string representation")); + } + } +} \ No newline at end of file diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index c7b67628f83..3cfccca9768 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -622,6 +622,7 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsDate()}}, {"g_injectXnullX_asDate", new List, ITraversal>> {(g,p) =>g.Inject(null).AsDate()}}, {"g_injectXinvalidstrX_asDate", new List, ITraversal>> {(g,p) =>g.Inject("This String is not an ISO 8601 Date").AsDate()}}, + {"g_injectX123X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(123).AsNumber()}}, {"g_injectX1_2X_asString", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString()}}, {"g_injectX1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString(Scope.Local)}}, {"g_injectXlist_1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsString(Scope.Local)}}, diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs index 5e2a49a166b..1f172f6acc1 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphBinary/GraphBinaryTests.cs @@ -565,6 +565,21 @@ public async Task TestMerge() Assert.Equal(expected, actual); } + + [Fact] + public async Task TestN() + { + var expected = N.Byte; + var writer = CreateGraphBinaryWriter(); + var reader = CreateGraphBinaryReader(); + var serializationStream = new MemoryStream(); + + await writer.WriteAsync(expected, serializationStream); + serializationStream.Position = 0; + var actual = await reader.ReadAsync(serializationStream); + + Assert.Equal(expected, actual); + } [Fact] public async Task TestOperator() diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs index 591e3b4378f..e5c1d8a88ab 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONReaderTests.cs @@ -336,6 +336,18 @@ public void ShouldDeserializeMerge(int version) Assert.Equal(Merge.OnMatch, deserializedValue); } + + [Theory, MemberData(nameof(Versions))] + public void ShouldDeserializeN(int version) + { + const string serializedValue = "{\"@type\":\"g:N\",\"@value\":\"byte\"}"; + var reader = CreateStandardGraphSONReader(version); + + var jsonElement = JsonSerializer.Deserialize(serializedValue); + var deserializedValue = reader.ToObject(jsonElement); + + Assert.Equal(N.Byte, deserializedValue); + } [Fact] public void ShouldDeserializePathFromGraphSON2() diff --git a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs index 98d924e2e10..34cdd8e534d 100644 --- a/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs +++ b/gremlin-dotnet/test/Gremlin.Net.UnitTest/Structure/IO/GraphSON/GraphSONWriterTests.cs @@ -300,6 +300,17 @@ public void ShouldSerializeMerge(int version) Assert.Equal(expectedGraphSON, serializedEnum); } + [Theory, MemberData(nameof(Versions))] + public void ShouldSerializeN(int version) + { + var writer = CreateGraphSONWriter(version); + + var serializedEnum = writer.WriteObject(N.Byte); + + var expectedGraphSON = "{\"@type\":\"g:N\",\"@value\":\"byte\"}"; + Assert.Equal(expectedGraphSON, serializedEnum); + } + [Theory, MemberData(nameof(Versions))] public void ShouldSerializeList(int version) { diff --git a/gremlin-go/driver/anonymousTraversal.go b/gremlin-go/driver/anonymousTraversal.go index a1b3a3f0b14..6cf32581bba 100644 --- a/gremlin-go/driver/anonymousTraversal.go +++ b/gremlin-go/driver/anonymousTraversal.go @@ -386,6 +386,11 @@ func (anonymousTraversal *anonymousTraversal) AsDate(args ...interface{}) *Graph return anonymousTraversal.graphTraversal().AsDate(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 --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go index 22de276a027..f89595c8212 100644 --- a/gremlin-go/driver/graphBinary.go +++ b/gremlin-go/driver/graphBinary.go @@ -82,6 +82,7 @@ const ( bulkSetType dataType = 0x2a mergeType dataType = 0x2e dtType dataType = 0x2f + nType dataType = 0x30 metricsType dataType = 0x2c traversalMetricsType dataType = 0x2d durationType dataType = 0x81 @@ -748,6 +749,8 @@ func (serializer *graphBinaryTypeSerializer) getType(val interface{}) (dataType, return mergeType, nil case dt: return dtType, nil + case n: + return nType, nil case p, Predicate: return pType, nil case textP, TextPredicate: diff --git a/gremlin-go/driver/graphTraversal.go b/gremlin-go/driver/graphTraversal.go index c3f7fca266f..b6b165f22d8 100644 --- a/gremlin-go/driver/graphTraversal.go +++ b/gremlin-go/driver/graphTraversal.go @@ -112,6 +112,12 @@ func (g *GraphTraversal) AsDate(args ...interface{}) *GraphTraversal { 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 --git a/gremlin-go/driver/serializer.go b/gremlin-go/driver/serializer.go index b7aa4a9750b..8a2dec9d56e 100644 --- a/gremlin-go/driver/serializer.go +++ b/gremlin-go/driver/serializer.go @@ -270,6 +270,7 @@ func initSerializers() { columnType: enumWriter, directionType: enumWriter, dtType: enumWriter, + nType: enumWriter, operatorType: enumWriter, orderType: enumWriter, pickType: enumWriter, @@ -327,6 +328,7 @@ func initDeserializers() { tType: enumReader, directionType: enumReader, dtType: enumReader, + nType: enumReader, bindingType: bindingReader, // Metrics diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go index 6d82f6d0af1..96cf361c07e 100644 --- a/gremlin-go/driver/traversal.go +++ b/gremlin-go/driver/traversal.go @@ -359,6 +359,40 @@ var Merge = merges{ InV: "inV", } + +type n string + +type ns struct { + // number type byte + NByte n + // number type short + NShort n + // number type int + NInt n + // number type long + NLong n + // number type float + NFloat n + // number type double + NDouble n + // number type bigInt + NBigInt n + // number type bigDecimal + NBigDecimal n +} + +// Merge is a set of operations for Vertex and Edge merging. +var N = ns{ + NByte: "nbyte", + NShort: "nshort", + NInt: "nint", + NLong: "nlong", + NFloat: "nfloat", + NDouble: "ndouble", + NBigInt: "nbigInt", + NBigDecimal: "nbigDecimal", +} + type operator string type operators struct { diff --git a/gremlin-javascript/build/generate.groovy b/gremlin-javascript/build/generate.groovy index 9d04bc3e1b0..7cf85b5c5d7 100644 --- a/gremlin-javascript/build/generate.groovy +++ b/gremlin-javascript/build/generate.groovy @@ -82,6 +82,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> 'const IO = traversalModule.IO;\n' + 'const DT = traversalModule.dt;\n' + 'const Merge = traversalModule.merge;\n' + + 'const N = traversalModule.n;\n' + 'const P = traversalModule.P;\n' + 'const Pick = traversalModule.pick\n' + 'const Pop = traversalModule.pop\n' + diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js index b2a831b0384..087a6c0a079 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js @@ -443,6 +443,16 @@ class GraphTraversal extends Traversal { return this; } + /** + * Graph traversal asNumber method. + * @param {...Object} args + * @returns {GraphTraversal} + */ + asNumber(...args) { + this.bytecode.addStep('asNumber', args); + return this; + } + /** * Graph traversal asString method. * @param {...Object} args diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js index a1ff9316dde..84c8482f8fd 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js @@ -512,6 +512,7 @@ module.exports = { column: toEnum('Column', 'keys values'), direction: toDirectionEnum('Direction', 'BOTH IN OUT from_ to'), dt: toEnum('DT', 'second minute hour day'), + n: toEnum('N', 'nbyte nshort nint nlong nfloat ndouble nbigint nbigdecimal'), graphSONVersion: toEnum('GraphSONVersion', 'V1_0 V2_0 V3_0'), gryoVersion: toEnum('GryoVersion', 'V1_0 V3_0'), merge: toEnum('Merge', 'onCreate onMatch outV inV'), diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/DataType.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/DataType.js index 272a848d877..ee12739a108 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/DataType.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/DataType.js @@ -75,6 +75,7 @@ const DataType = { TRAVERSALMETRICS: 0x2d, MERGE: 0x2e, DT: 0x2f, + N: 0x30, CHAR: 0x80, DURATION: 0x81, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/EnumSerializer.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/EnumSerializer.js index 03627772da6..bb11c65cf83 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/EnumSerializer.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/io/binary/internals/EnumSerializer.js @@ -44,6 +44,7 @@ module.exports = class EnumSerializer { { name: 'Direction', code: DT.DIRECTION, enum: to_orig_enum(t.direction) }, { name: 'DT', code: DT.DT, enum: to_orig_enum(t.dt) }, { name: 'Merge', code: DT.MERGE, enum: to_orig_enum(t.merge) }, + { name: 'N', code: DT.N, enum: to_orig_enum(t.n) }, { name: 'Operator', code: DT.OPERATOR, enum: to_orig_enum(t.operator) }, { name: 'Order', code: DT.ORDER, enum: to_orig_enum(t.order) }, { name: 'Pick', code: DT.PICK, enum: to_orig_enum(t.pick) }, 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 e1a496e8464..2dc4eb664f4 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 @@ -52,6 +52,7 @@ const Direction = { const IO = traversalModule.IO; const DT = traversalModule.dt; const Merge = traversalModule.merge; +const N = traversalModule.n; const P = traversalModule.P; const Pick = traversalModule.pick const Pop = traversalModule.pop @@ -622,6 +623,26 @@ const gremlins = { g_injectX1_2X_asDate: [function({g, xx1}) { return g.inject(xx1).asDate() }], g_injectXnullX_asDate: [function({g}) { return g.inject(null).asDate() }], g_injectXinvalidstrX_asDate: [function({g}) { return g.inject("This String is not an ISO 8601 Date").asDate() }], + g_injectX5bX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5sX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5iX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5lX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5nX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5_0X_asNumberXX: [function({g}) { return g.inject(5.0).asNumber() }], + g_injectX5_75fX_asNumberXX: [function({g}) { return g.inject(5.75).asNumber() }], + g_injectX5_43X_asNumberXN_nintX: [function({g}) { return g.inject(5.43).asNumber(N.nint) }], + g_injectX5_67X_asNumberXN_nintX: [function({g}) { return g.inject(5.67).asNumber(N.nint) }], + g_injectX5X_asNumberXN_nlongX: [function({g}) { return g.inject(5).asNumber(N.nlong) }], + g_injectX12X_asNumberXN_nbyteX: [function({g}) { return g.inject(12).asNumber(N.nbyte) }], + g_injectX32768X_asNumberXN_nshortX: [function({g}) { return g.inject(32768).asNumber(N.nshort) }, 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_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() }], + g_injectX_1__2__3__4_X_unfoldXX_asNumberXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber() }], + g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber().fold() }], g_injectX1_2X_asString: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString() }], g_injectX1_2X_asStringXlocalX: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString(Scope.local) }], g_injectXlist_1_2X_asStringXlocalX: [function({g, xx1}) { return g.inject(xx1).asString(Scope.local) }], diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4 index 0f22d6ec592..4f089e9cfde 100644 --- a/gremlin-language/src/main/antlr4/Gremlin.g4 +++ b/gremlin-language/src/main/antlr4/Gremlin.g4 @@ -307,6 +307,7 @@ traversalMethod | traversalMethod_asDate | traversalMethod_dateAdd | traversalMethod_dateDiff + | traversalMethod_asNumber ; traversalMethod_V @@ -353,6 +354,11 @@ traversalMethod_asDate : K_ASDATE LPAREN RPAREN ; + traversalMethod_asNumber + : K_ASNUMBER LPAREN RPAREN #traversalMethod_asNumber_Empty + | K_ASNUMBER LPAREN traversalN RPAREN #traversalMethod_asNumber_traversalN + ; + traversalMethod_asString : K_ASSTRING LPAREN RPAREN #traversalMethod_asString_Empty | K_ASSTRING LPAREN traversalScope RPAREN #traversalMethod_asString_Scope @@ -1071,6 +1077,17 @@ traversalDT | K_DAY | K_DT DOT K_DAY ; +traversalN + : K_BYTE | K_N DOT K_BYTE + | K_SHORT | K_N DOT K_SHORT + | K_INT | K_N DOT K_INT + | K_LONG | K_N DOT K_LONG + | K_FLOAT | K_N DOT K_FLOAT + | K_DOUBLE | K_N DOT K_DOUBLE + | K_BIGINT | K_N DOT K_BIGINT + | K_BIGDECIMAL | K_N DOT K_BIGDECIMAL + ; + traversalPredicate : traversalPredicate_eq | traversalPredicate_neq @@ -1569,6 +1586,7 @@ genericLiteral | traversalMerge | traversalPick | traversalDT + | traversalN | structureVertexLiteral | genericSetLiteral | genericCollectionLiteral @@ -1683,18 +1701,22 @@ keyword | K_AS | K_ASC | K_ASDATE + | K_ASNUMBER | K_ASSTRING | K_ASSIGN | K_BARRIER | K_BARRIERU | K_BEGIN | K_BETWEEN + | K_BIGINT + | K_BIGDECIMAL | K_BOTH | K_BOTHU | K_BOTHE | K_BOTHV | K_BRANCH | K_BY + | K_BYTE | K_CALL | K_CAP | K_CARDINALITY @@ -1726,6 +1748,7 @@ keyword | K_DISJUNCT | K_DISTANCE | K_DIV + | K_DOUBLE | K_DROP | K_DT | K_E @@ -1741,6 +1764,7 @@ keyword | K_FILTER | K_FIRST | K_FLATMAP + | K_FLOAT | K_FOLD | K_FORMAT | K_FROM @@ -1773,6 +1797,7 @@ keyword | K_INFINITY | K_INJECT | K_INSIDE + | K_INT | K_INTERSECT | K_INV | K_IO @@ -1788,6 +1813,7 @@ keyword | K_LIMIT | K_LIST | K_LOCAL + | K_LONG | K_LOOPS | K_LT | K_LTE @@ -1807,6 +1833,7 @@ keyword | K_MINUS | K_MIXED | K_MULT + | K_N | K_NAN | K_NEGATE | K_NEW @@ -1865,6 +1892,7 @@ keyword | K_SECOND | K_SELECT | K_SET + | K_SHORT | K_SHORTESTPATH | K_SHORTESTPATHU | K_SHUFFLE @@ -1944,18 +1972,22 @@ K_ANY: 'any'; K_AS: 'as'; K_ASC: 'asc'; K_ASDATE: 'asDate'; +K_ASNUMBER: 'asNumber'; K_ASSTRING: 'asString'; K_ASSIGN: 'assign'; K_BARRIER: 'barrier'; K_BARRIERU: 'Barrier'; K_BEGIN: 'begin'; K_BETWEEN: 'between'; +K_BIGDECIMAL: 'nbigDecimal'; +K_BIGINT: 'nbigInt'; K_BOTH: 'both'; K_BOTHU: 'BOTH'; K_BOTHE: 'bothE'; K_BOTHV: 'bothV'; K_BRANCH: 'branch'; K_BY: 'by'; +K_BYTE: 'nbyte'; K_CALL: 'call'; K_CAP: 'cap'; K_CARDINALITY: 'Cardinality'; @@ -1987,6 +2019,7 @@ K_DIRECTION: 'Direction'; K_DISJUNCT: 'disjunct'; K_DISTANCE: 'distance'; K_DIV: 'div'; +K_DOUBLE: 'ndouble'; K_DROP: 'drop'; K_DT: 'DT'; K_E: 'E'; @@ -2002,6 +2035,7 @@ K_FALSE: 'false'; K_FILTER: 'filter'; K_FIRST: 'first'; K_FLATMAP: 'flatMap'; +K_FLOAT: 'nfloat'; K_FOLD: 'fold'; K_FORMAT: 'format'; K_FROM: 'from'; @@ -2034,6 +2068,7 @@ K_INDEX: 'index'; K_INFINITY: 'Infinity'; K_INJECT: 'inject'; K_INSIDE: 'inside'; +K_INT: 'nint'; K_INTERSECT: 'intersect'; K_INV: 'inV'; K_IOU: 'IO'; @@ -2049,6 +2084,7 @@ K_LENGTH: 'length'; K_LIMIT: 'limit'; K_LIST: 'list'; K_LOCAL: 'local'; +K_LONG: 'nlong'; K_LOOPS: 'loops'; K_LT: 'lt'; K_LTE: 'lte'; @@ -2068,6 +2104,7 @@ K_MINUTE: 'minute'; K_MINUS: 'minus'; K_MIXED: 'mixed'; K_MULT: 'mult'; +K_N: 'N'; K_NAN: 'NaN'; K_NEGATE: 'negate'; K_NEXT: 'next'; @@ -2129,6 +2166,7 @@ K_SET: 'set'; K_SHORTESTPATHU: 'ShortestPath'; K_SHORTESTPATH: 'shortestPath'; K_SHUFFLE: 'shuffle'; +K_SHORT: 'nshort'; K_SIDEEFFECT: 'sideEffect'; K_SIMPLEPATH: 'simplePath'; K_SINGLE: 'single'; 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 new file mode 100644 index 00000000000..7fef5997f13 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/AsNumber.feature @@ -0,0 +1,275 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +@StepClassMap @StepAsNumber +Feature: Step - asNumber() + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5bX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5b).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].b | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5sX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5s).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].s | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5iX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5i).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].i | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5lX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5l).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].l | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5nX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5n).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].n | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5_0X_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5.0).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].d | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5_75fX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject(5.75f).asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5.75].d | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5_43X_asNumberXN_nintX + Given the empty graph + And the traversal of + """ + g.inject(5.43).asNumber(N.nint) + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].i | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5_67X_asNumberXN_nintX + Given the empty graph + And the traversal of + """ + g.inject(5.67).asNumber(N.nint) + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].i | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5X_asNumberXN_nlongX + Given the empty graph + And the traversal of + """ + g.inject(5).asNumber(N.nlong) + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].l | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX12X_asNumberXN_nbyteX + Given the empty graph + And the traversal of + """ + g.inject(12).asNumber(N.nbyte) + """ + When iterated to list + Then the result should be unordered + | result | + | d[12].b | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX32768X_asNumberXN_nshortX + Given the empty graph + And the traversal of + """ + g.inject(32768).asNumber(N.nshort) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX300X_asNumberXN_nbyteX + Given the empty graph + And the traversal of + """ + g.inject(300).asNumber(N.nbyte) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5X_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject("5").asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].b | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX5X_asNumberXN_nintX + Given the empty graph + And the traversal of + """ + g.inject("5").asNumber(N.nint) + """ + When iterated to list + Then the result should be unordered + | result | + | d[5].i | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX1_000X_asNumberXN_nintX + Given the empty graph + And the traversal of + """ + g.inject("1,000").asNumber(N.nint) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can not parse number: '1,000'" + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX32768X_asNumberXN_nshortX + Given the empty graph + And the traversal of + """ + g.inject("32768").asNumber(N.nshort) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectXtestX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject("test").asNumber() + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can not parse number: 'test'" + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX_1__2__3__4_X_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject([1, 2, 3, 4]).asNumber() + """ + When iterated to list + Then the traversal will raise an error with message containing text of "Can't parse 'ArrayList' as number." + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX_1__2__3__4_X_unfoldXX_asNumberXX + Given the empty graph + And the traversal of + """ + g.inject([1, 2, 3, 4]).unfold().asNumber() + """ + When iterated to list + Then the result should be unordered + | result | + | d[1].i | + | d[2].i | + | d[3].i | + | d[4].i | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX + Given the empty graph + And the traversal of + """ + g.inject([1, 2, 3, 4]).unfold().asNumber().fold() + """ + When iterated to list + Then the result should be unordered + | result | + | l[d[1].i,d[2].i,d[3].i,d[4].i] | + + @GraphComputerVerificationInjectionNotSupported + Scenario: g_VX1X_asNumberXN_nintX + Given the empty graph + And the traversal of + """ + g.V(1).asNumber(N.nint) + """ + When iterated to list + Then the result should be unordered + | result | + | d[1].i | +# ==> Parsing Exception ("Type Vertex is not parsable to Type Integer") \ No newline at end of file From 8bee410b6362982bab7377c76bb99821b0667e25 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Mon, 7 Jul 2025 12:52:51 -0400 Subject: [PATCH 2/9] minor adjustments to implementation and error messages, fix issues in GLVs --- .../language/translator/TranslateVisitor.java | 7 + .../gremlin/process/traversal/N.java | 25 ++- .../traversal/step/map/AsNumberStep.java | 194 +++++++++--------- .../traversal/step/map/AsNumberStepTest.java | 45 ++++ .../Gherkin/Gremlin.cs | 22 +- gremlin-go/driver/cucumber/gremlin.go | 21 ++ gremlin-go/driver/graphBinary.go | 2 +- gremlin-go/driver/traversal.go | 36 ++-- .../test/cucumber/gremlin.js | 3 +- .../unit/graphbinary/AnySerializer-test.js | 9 +- gremlin-python/build/generate.groovy | 2 +- .../gremlin_python/process/graph_traversal.py | 14 ++ .../gremlin_python/process/traversal.py | 10 + .../structure/io/graphbinaryV1.py | 8 +- .../src/main/python/radish/gremlin.py | 23 ++- .../driver/test_driver_remote_connection.py | 2 +- .../test/features/map/AsNumber.feature | 27 +-- 17 files changed, 304 insertions(+), 146 deletions(-) create mode 100644 gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java index 2dc4b5a49e9..317dd098d5d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/TranslateVisitor.java @@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -207,6 +208,12 @@ public Void visitTraversalDT(final GremlinParser.TraversalDTContext ctx) { return null; } + @Override + public Void visitTraversalN(final GremlinParser.TraversalNContext ctx) { + appendExplicitNaming(ctx.getText(), N.class.getSimpleName()); + return null; + } + @Override public Void visitTraversalPredicate(final GremlinParser.TraversalPredicateContext ctx) { switch(ctx.getChildCount()) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java index d0bf66bfdc5..67eda53a7d9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java @@ -25,12 +25,21 @@ * Used with {@link AsNumberStep} step. */ public enum N { - nbyte, - nshort, - nint, - nlong, - nfloat, - ndouble, - nbigInt, - nbigDecimal + nbyte("Byte"), + nshort("Short"), + nint("Integer"), + nlong("Long"), + nfloat("Float"), + ndouble("Double"), + nbigInt("BigInt"), + nbigDecimal("BigDecimal"),; + + private final String typeName; + + N(String name) {typeName = name;} + + @Override + public String toString() { + return typeName; + } } \ No newline at end of file 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 4c1bfe4e9f2..1fe1a52051b 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 @@ -36,97 +36,63 @@ import java.util.Set; /** - * Reference implementation for date concatenation step. - * - * @author Valentyn Kahamlyk + * Reference implementation for number parsing step. */ public final class AsNumberStep extends ScalarMapStep { - private final N numberToken; - private final boolean auto; + private N numberToken; public AsNumberStep(final Traversal.Admin traversal) { super(traversal); - this.numberToken = N.nlong; - this.auto = true; + this.numberToken = null; } public AsNumberStep(final Traversal.Admin traversal, final N numberToken) { super(traversal); this.numberToken = numberToken; - this.auto = false; } - private static int getNumberBitsBasedOnValue(Number number) { - final Class clazz = number.getClass(); - if (clazz.equals(BigInteger.class)) { - return 128; - } else if (clazz.equals(BigDecimal.class)) { - return 128; - } - boolean floatingPoint = (clazz.equals(Float.class) || clazz.equals(Double.class)); - if (!floatingPoint && (number.longValue() >= Byte.MIN_VALUE) && (number.longValue() <= Byte.MAX_VALUE)) { - return 8; - } else if (!floatingPoint && (number.longValue() >= Short.MIN_VALUE) && (number.longValue() <= Short.MAX_VALUE)) { - return 16; - } else if (!floatingPoint && (number.longValue() >= Integer.MIN_VALUE) && (number.longValue() <= Integer.MAX_VALUE)) { - return 32; - } else if (floatingPoint && (number.doubleValue() >= Float.MIN_VALUE) && (number.doubleValue() <= Float.MAX_VALUE)) { - return 32; - } else { - return 64; + @Override + protected Number map(Traverser.Admin traverser) { + final Object object = traverser.get(); + if (object instanceof String) { + String numberText = (String) object; + Number number = parseNumber(numberText); + return numberToken == null ? autoNumber(number) : castNumber(number, numberToken); + } else if (object instanceof Number) { + Number number = (Number) object; + return numberToken == null ? autoNumber(number) : castNumber(number, numberToken); } + throw new IllegalArgumentException(String.format("Can't parse type %s as number.", object == null ? "null" : object.getClass().getSimpleName())); } - private static int getNumberTokenBits(N numberToken) { - if (numberToken == N.nbyte) { - return 8; - } else if (numberToken == N.nshort) { - return 16; - } else if (numberToken == N.nint) { - return 32; - } else if (numberToken == N.nlong) { - return 64; - } else if (numberToken == N.nfloat) { - return 32; - } else if (numberToken == N.ndouble) { - return 64; - } - return 128; + @Override + public Set getRequirements() { + return Collections.singleton(TraverserRequirement.OBJECT); } - private static Number castNumber(Number number, N numberToken) { - int sourceBits = getNumberBitsBasedOnValue(number); - int targetBits = getNumberTokenBits(numberToken); - if (sourceBits > targetBits) { - throw new ArithmeticException("Can not convert number type as would cause overflow."); - } - if (numberToken == N.nbyte) { - return number.byteValue(); - } else if (numberToken == N.nshort) { - return number.shortValue(); - } else if (numberToken == N.nint) { - return number.intValue(); - } else if (numberToken == N.nlong) { - return number.longValue(); - } else if (numberToken == N.nfloat) { - return number.floatValue(); - } else if (numberToken == N.ndouble) { - return number.doubleValue(); - } else if (numberToken == N.nbigInt) { - return BigInteger.valueOf(number.longValue()); - } else if (numberToken == N.nbigDecimal) { - return BigDecimal.valueOf(number.doubleValue()); - } - return number; + @Override + public void setTraversal(final Traversal.Admin parentTraversal) { + super.setTraversal(parentTraversal); } - private static Number autoNumber(Number number) { - final Class clazz = number.getClass(); - if (clazz.equals(Float.class)) { - return castNumber(number, N.ndouble); - } - return number; + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + (numberToken != null ? numberToken.hashCode() : 0); + return result; + } + + @Override + public AsNumberStep clone() { + final AsNumberStep clone = (AsNumberStep) super.clone(); + clone.numberToken = this.numberToken; + return clone; + } + + @Override + public String toString() { + return StringFactory.stepString(this); } public static Number parseNumber(final String value) { @@ -151,36 +117,80 @@ public static Number parseNumber(final String value) { } return result; } catch (NumberFormatException nfe) { - throw new NumberFormatException(String.format("Can not parse number: '%s'", value)); + throw new NumberFormatException(String.format("Can't parse string '%s' as number.", value)); } } - @Override - protected Number map(Traverser.Admin traverser) { - final Object object = traverser.get(); - if (object instanceof String) { - String numberText = (String) object; - Number number = parseNumber(numberText); - return auto ? autoNumber(number) : castNumber(number, numberToken); - } else if (object instanceof Number) { - Number number = (Number) object; - return auto ? autoNumber(number) : castNumber(number, numberToken); + private static Number autoNumber(Number number) { + final Class clazz = number.getClass(); + if (clazz.equals(Float.class)) { + return castNumber(number, N.ndouble); } - throw new IllegalArgumentException(String.format("Can't parse '%s' as number.", object == null ? "null" : object.getClass().getSimpleName())); + return number; } - @Override - public Set getRequirements() { - return Collections.singleton(TraverserRequirement.OBJECT); + private static Number castNumber(Number number, N numberToken) { + int sourceBits = getNumberBitsBasedOnValue(number); + int targetBits = getNumberTokenBits(numberToken); + if (sourceBits > targetBits) { + throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", + number.getClass().getSimpleName(), numberToken.toString())); + } + if (numberToken == N.nbyte) { + return number.byteValue(); + } else if (numberToken == N.nshort) { + return number.shortValue(); + } else if (numberToken == N.nint) { + return number.intValue(); + } else if (numberToken == N.nlong) { + return number.longValue(); + } else if (numberToken == N.nfloat) { + return number.floatValue(); + } else if (numberToken == N.ndouble) { + return number.doubleValue(); + } else if (numberToken == N.nbigInt) { + return BigInteger.valueOf(number.longValue()); + } else if (numberToken == N.nbigDecimal) { + return BigDecimal.valueOf(number.doubleValue()); + } + return number; } - @Override - public void setTraversal(final Traversal.Admin parentTraversal) { - super.setTraversal(parentTraversal); + private static int getNumberBitsBasedOnValue(Number number) { + final Class clazz = number.getClass(); + if (clazz.equals(BigInteger.class)) { + return 128; + } else if (clazz.equals(BigDecimal.class)) { + return 128; + } + boolean floatingPoint = (clazz.equals(Float.class) || clazz.equals(Double.class)); + if (!floatingPoint && (number.longValue() >= Byte.MIN_VALUE) && (number.longValue() <= Byte.MAX_VALUE)) { + return 8; + } else if (!floatingPoint && (number.longValue() >= Short.MIN_VALUE) && (number.longValue() <= Short.MAX_VALUE)) { + return 16; + } else if (!floatingPoint && (number.longValue() >= Integer.MIN_VALUE) && (number.longValue() <= Integer.MAX_VALUE)) { + return 32; + } else if (floatingPoint && (number.doubleValue() >= Float.MIN_VALUE) && (number.doubleValue() <= Float.MAX_VALUE)) { + return 32; + } else { + return 64; + } } - @Override - public String toString() { - return StringFactory.stepString(this); + private static int getNumberTokenBits(N numberToken) { + if (numberToken == N.nbyte) { + return 8; + } else if (numberToken == N.nshort) { + return 16; + } else if (numberToken == N.nint) { + return 32; + } else if (numberToken == N.nlong) { + return 64; + } else if (numberToken == N.nfloat) { + return 32; + } else if (numberToken == N.ndouble) { + return 64; + } + return 128; } } 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 new file mode 100644 index 00000000000..a812aac8270 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AsNumberStepTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.process.traversal.step.map; + +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; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +public class AsNumberStepTest extends StepTest { + + @Override + protected List getTraversals() { + return Collections.singletonList(__.asNumber()); + } + + @Test + public void testReturnTypes() { + assertEquals(1, __.__(1).asNumber().next()); + } + +} diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 3cfccca9768..25ba75311e7 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -622,7 +622,27 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsDate()}}, {"g_injectXnullX_asDate", new List, ITraversal>> {(g,p) =>g.Inject(null).AsDate()}}, {"g_injectXinvalidstrX_asDate", new List, ITraversal>> {(g,p) =>g.Inject("This String is not an ISO 8601 Date").AsDate()}}, - {"g_injectX123X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(123).AsNumber()}}, + {"g_injectX5bX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject((byte) 5).AsNumber()}}, + {"g_injectX5sX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject((short) 5).AsNumber()}}, + {"g_injectX5iX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber()}}, + {"g_injectX5lX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5l).AsNumber()}}, + {"g_injectX5nX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new BigInteger(5)).AsNumber()}}, + {"g_injectX5_0X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5.0).AsNumber()}}, + {"g_injectX5_75fX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5.75f).AsNumber()}}, + {"g_injectX5_43X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.43).AsNumber(N.Nint)}}, + {"g_injectX5_67X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.67).AsNumber(N.Nint)}}, + {"g_injectX5X_asNumberXN_nlongX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber(N.Nlong)}}, + {"g_injectX12X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(12).AsNumber(N.Nbyte)}}, + {"g_injectX32768X_asNumberXN_nshortX", new List, ITraversal>> {(g,p) =>g.Inject(32768).AsNumber(N.Nshort)}}, + {"g_injectX300X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(300).AsNumber(N.Nbyte)}}, + {"g_injectX5X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber()}}, + {"g_injectX5X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Nint)}}, + {"g_injectX1_000X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("1,000").AsNumber(N.Nint)}}, + {"g_injectXtestX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("test").AsNumber()}}, + {"g_injectX_1__2__3__4_X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).AsNumber()}}, + {"g_injectX_1__2__3__4_X_unfoldXX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber()}}, + {"g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber().Fold()}}, + {"g_VX1X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(null).AsNumber(N.Nint)}}, {"g_injectX1_2X_asString", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString()}}, {"g_injectX1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString(Scope.Local)}}, {"g_injectXlist_1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsString(Scope.Local)}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 57534a17c37..8612cdbf5e4 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -592,6 +592,27 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_injectX1_2X_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).AsDate()}}, "g_injectXnullX_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsDate()}}, "g_injectXinvalidstrX_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("This String is not an ISO 8601 Date").AsDate()}}, + "g_injectX5bX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5sX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5iX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5lX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5nX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5_0X_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.0).AsNumber()}}, + "g_injectX5_75fX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.75).AsNumber()}}, + "g_injectX5_43X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.43).AsNumber(gremlingo.N.Nint)}}, + "g_injectX5_67X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.67).AsNumber(gremlingo.N.Nint)}}, + "g_injectX5X_asNumberXN_nlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber(gremlingo.N.Nlong)}}, + "g_injectX12X_asNumberXN_nbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(12).AsNumber(gremlingo.N.Nbyte)}}, + "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_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()}}, + "g_injectX_1__2__3__4_X_unfoldXX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber()}}, + "g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber().Fold()}}, + "g_VX1X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsNumber(gremlingo.N.Nint)}}, "g_injectX1_2X_asString": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString()}}, "g_injectX1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString(gremlingo.Scope.Local)}}, "g_injectXlist_1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).AsString(gremlingo.Scope.Local)}}, diff --git a/gremlin-go/driver/graphBinary.go b/gremlin-go/driver/graphBinary.go index f89595c8212..fd983ea80dc 100644 --- a/gremlin-go/driver/graphBinary.go +++ b/gremlin-go/driver/graphBinary.go @@ -82,7 +82,7 @@ const ( bulkSetType dataType = 0x2a mergeType dataType = 0x2e dtType dataType = 0x2f - nType dataType = 0x30 + nType dataType = 0x30 metricsType dataType = 0x2c traversalMetricsType dataType = 0x2d durationType dataType = 0x81 diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go index 96cf361c07e..bd21fb6a063 100644 --- a/gremlin-go/driver/traversal.go +++ b/gremlin-go/driver/traversal.go @@ -330,7 +330,7 @@ type dts struct { Day dt } -// Merge is a set of operations for Vertex and Edge merging. +// DT is a set of operations for calculating date var DT = dts{ Second: "second", Minute: "minute", @@ -364,33 +364,33 @@ type n string type ns struct { // number type byte - NByte n + Nbyte n // number type short - NShort n + Nshort n // number type int - NInt n + Nint n // number type long - NLong n + Nlong n // number type float - NFloat n + Nfloat n // number type double - NDouble n + Ndouble n // number type bigInt - NBigInt n + NbigInt n // number type bigDecimal - NBigDecimal n + NbigDecimal n } -// Merge is a set of operations for Vertex and Edge merging. +// N is a set of operations for denoting number types during conversion. var N = ns{ - NByte: "nbyte", - NShort: "nshort", - NInt: "nint", - NLong: "nlong", - NFloat: "nfloat", - NDouble: "ndouble", - NBigInt: "nbigInt", - NBigDecimal: "nbigDecimal", + Nbyte: "nbyte", + Nshort: "nshort", + Nint: "nint", + Nlong: "nlong", + Nfloat: "nfloat", + Ndouble: "ndouble", + NbigInt: "nbigInt", + NbigDecimal: "nbigDecimal", } type operator string 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 2dc4eb664f4..5f75840cd2d 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 @@ -634,7 +634,7 @@ const gremlins = { g_injectX5_67X_asNumberXN_nintX: [function({g}) { return g.inject(5.67).asNumber(N.nint) }], g_injectX5X_asNumberXN_nlongX: [function({g}) { return g.inject(5).asNumber(N.nlong) }], g_injectX12X_asNumberXN_nbyteX: [function({g}) { return g.inject(12).asNumber(N.nbyte) }], - g_injectX32768X_asNumberXN_nshortX: [function({g}) { return g.inject(32768).asNumber(N.nshort) }, function({g}) { return g.inject("32768").asNumber(N.nshort) }], + 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) }], @@ -643,6 +643,7 @@ const gremlins = { g_injectX_1__2__3__4_X_asNumberXX: [function({g}) { return g.inject([1, 2, 3, 4]).asNumber() }], g_injectX_1__2__3__4_X_unfoldXX_asNumberXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber() }], g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber().fold() }], + g_VX1X_asNumberXN_nintX: [function({g}) { return g.inject(null).asNumber(N.nint) }], g_injectX1_2X_asString: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString() }], g_injectX1_2X_asStringXlocalX: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString(Scope.local) }], g_injectXlist_1_2X_asStringXlocalX: [function({g, xx1}) { return g.inject(xx1).asString(Scope.local) }], diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js index ae7518f13aa..b9399a6ffff 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js @@ -176,6 +176,9 @@ describe('GraphBinary.AnySerializer', () => { { v: new t.EnumValue('Merge', 'onMatch'), b: [ DataType.MERGE,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x07, ...from('onMatch') ] }, + { v: new t.EnumValue('N', 'nbyte'), + b: [ DataType.N,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x05, ...from('nbyte') ] + }, { v: new t.EnumValue('Operator', 'addAll'), b: [ DataType.OPERATOR,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x06, ...from('addAll') ] }, @@ -355,7 +358,7 @@ describe('GraphBinary.AnySerializer', () => { { err:/buffer is empty/, b:[] }, { err:/buffer is empty/, b:[] }, - { err:/unknown {type_code}/, b:[0x30] }, + { err:/unknown {type_code}/, b:[0x40] }, // tests an unassigned type code { err:/unknown {type_code}/, b:[0x8F] }, { err:/unknown {type_code}/, b:[0xFF] }, @@ -517,6 +520,10 @@ describe('GraphBinary.AnySerializer', () => { { v:null, b:[0x2e,0x01] }, { v:new t.EnumValue('Merge','onCreate'), b:[0x2e,0x00, 0x03,0x00, 0x00,0x00,0x00,0x08, ...from('onCreate')] }, + // N + { v:null, b:[0x30,0x01] }, + { v:new t.EnumValue('N','nbyte'), b:[0x30,0x00, 0x03,0x00, 0x00,0x00,0x00,0x05, ...from('nbyte')] }, + // OPERATOR { v:null, b:[0x19,0x01] }, { v:new t.EnumValue('Operator','addAll'), b:[0x19,0x00, 0x03,0x00, 0x00,0x00,0x00,0x06, ...from('addAll')] }, diff --git a/gremlin-python/build/generate.groovy b/gremlin-python/build/generate.groovy index 6da003cc54a..45e204288eb 100644 --- a/gremlin-python/build/generate.groovy +++ b/gremlin-python/build/generate.groovy @@ -63,7 +63,7 @@ radishGremlinFile.withWriter('UTF-8') { Writer writer -> 'from gremlin_python.process.traversal import TraversalStrategy\n' + 'from gremlin_python.process.graph_traversal import __\n' + 'from gremlin_python.structure.graph import Graph\n' + - 'from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, DT, Merge, T, Pick, Operator, IO, WithOptions\n') + 'from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, DT, Merge, N, T, Pick, Operator, IO, WithOptions\n') // some traversals may require a static translation if the translator can't handle them for some reason def staticTranslate = [:] diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py index f1f55f089c0..ced9fc18909 100644 --- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py +++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py @@ -318,6 +318,10 @@ def as_date(self, *args): self.bytecode.add_step("asDate", *args) return self + def as_number(self, *args): + self.bytecode.add_step("asNumber", *args) + return self + def as_string(self, *args): self.bytecode.add_step("asString", *args) return self @@ -1099,6 +1103,10 @@ def as_(cls, *args): def as_date(cls, *args): return cls.graph_traversal(None, None, Bytecode()).as_date(*args) + @classmethod + def as_number(cls, *args): + return cls.graph_traversal(None, None, Bytecode()).as_number(*args) + @classmethod def as_string(cls, *args): return cls.graph_traversal(None, None, Bytecode()).as_string(*args) @@ -1871,6 +1879,10 @@ def as_date(*args): return __.as_date(*args) +def as_number(*args): + return __.as_number(*args) + + def as_string(*args): return __.as_string(*args) @@ -2415,6 +2427,8 @@ def where(*args): 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) diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py index fb744333483..4f93bf97add 100644 --- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py +++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py @@ -208,6 +208,16 @@ def process(f): statics.add_static('in_v', Merge.in_v) statics.add_static('out_v', Merge.out_v) +N = Enum('N', ' nbyte nshort nint nlong nfloat ndouble nbigInt nbigDecimal') +statics.add_static('nbyte', N.nbyte) +statics.add_static('nshort', N.nshort) +statics.add_static('nint', N.nint) +statics.add_static('nlong', N.nlong) +statics.add_static('nfloat', N.nfloat) +statics.add_static('ndouble', N.ndouble) +statics.add_static('nbigInt', N.nbigInt) +statics.add_static('nbigDecimal', N.nbigDecimal) + Order = Enum('Order', ' asc desc shuffle') statics.add_static('shuffle', Order.shuffle) diff --git a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py index d74561bb8af..fa1e69352ff 100644 --- a/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py +++ b/gremlin-python/src/main/python/gremlin_python/structure/io/graphbinaryV1.py @@ -33,7 +33,7 @@ from gremlin_python.statics import FloatType, BigDecimal, FunctionType, ShortType, IntType, LongType, BigIntType, \ TypeType, DictType, ListType, SetType, SingleByte, ByteBufferType, GremlinType, \ SingleChar -from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, DT, Merge, \ +from gremlin_python.process.traversal import Barrier, Binding, Bytecode, Cardinality, Column, Direction, DT, Merge, N, \ Operator, Order, Pick, Pop, P, Scope, TextP, Traversal, Traverser, \ TraversalStrategy, T from gremlin_python.process.graph_traversal import GraphTraversal @@ -98,6 +98,7 @@ class DataType(Enum): traversalmetrics = 0x2d merge = 0x2e dt = 0x2f + n = 0x30 char = 0x80 duration = 0x81 inetaddress = 0x82 # todo @@ -977,6 +978,11 @@ class MergeIO(_EnumIO): python_type = Merge +class NIO(_EnumIO): + graphbinary_type = DataType.n + python_type = N + + class ScopeIO(_EnumIO): graphbinary_type = DataType.scope python_type = Scope diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index 5d4b19fa135..8c2134f5926 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -33,7 +33,7 @@ from gremlin_python.process.traversal import TraversalStrategy from gremlin_python.process.graph_traversal import __ from gremlin_python.structure.graph import Graph -from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, DT, Merge, T, Pick, Operator, IO, WithOptions +from gremlin_python.process.traversal import Barrier, Cardinality, CardinalityValue, P, TextP, Pop, Scope, Column, Order, Direction, DT, Merge, N, T, Pick, Operator, IO, WithOptions world.gremlins = { 'g_V_branchXlabel_isXpersonX_countX_optionX1__ageX_optionX0__langX_optionX0__nameX': [(lambda g, xx1=None,xx2=None:g.V().branch(__.label().is_('person').count()).option(xx1, __.values('age')).option(xx2, __.values('lang')).option(xx2, __.values('name')))], @@ -595,6 +595,27 @@ 'g_injectX1_2X_asDate': [(lambda g, xx1=None:g.inject(xx1).as_date())], 'g_injectXnullX_asDate': [(lambda g:g.inject(None).as_date())], 'g_injectXinvalidstrX_asDate': [(lambda g:g.inject('This String is not an ISO 8601 Date').as_date())], + 'g_injectX5bX_asNumberXX': [(lambda g:g.inject(5).as_number())], + 'g_injectX5sX_asNumberXX': [(lambda g:g.inject(5).as_number())], + 'g_injectX5iX_asNumberXX': [(lambda g:g.inject(5).as_number())], + 'g_injectX5lX_asNumberXX': [(lambda g:g.inject(long(5)).as_number())], + 'g_injectX5nX_asNumberXX': [(lambda g:g.inject(bigint(5)).as_number())], + 'g_injectX5_0X_asNumberXX': [(lambda g:g.inject(5.0).as_number())], + 'g_injectX5_75fX_asNumberXX': [(lambda g:g.inject(5.75).as_number())], + 'g_injectX5_43X_asNumberXN_nintX': [(lambda g:g.inject(5.43).as_number(N.nint))], + 'g_injectX5_67X_asNumberXN_nintX': [(lambda g:g.inject(5.67).as_number(N.nint))], + 'g_injectX5X_asNumberXN_nlongX': [(lambda g:g.inject(5).as_number(N.nlong))], + 'g_injectX12X_asNumberXN_nbyteX': [(lambda g:g.inject(12).as_number(N.nbyte))], + '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_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())], + 'g_injectX_1__2__3__4_X_unfoldXX_asNumberXX': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number())], + 'g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number().fold())], + 'g_VX1X_asNumberXN_nintX': [(lambda g:g.inject(None).as_number(N.nint))], 'g_injectX1_2X_asString': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string())], 'g_injectX1_2X_asStringXlocalX': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string(Scope.local))], 'g_injectXlist_1_2X_asStringXlocalX': [(lambda g, xx1=None:g.inject(xx1).as_string(Scope.local))], diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py index eb6beee5c63..fe402a10917 100644 --- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py +++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py @@ -25,7 +25,7 @@ from gremlin_python.process.traversal import Traverser from gremlin_python.process.traversal import TraversalStrategy from gremlin_python.process.traversal import Bindings -from gremlin_python.process.traversal import P, Order, T +from gremlin_python.process.traversal import P, Order, T, N from gremlin_python.process.graph_traversal import __ from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.structure.graph import Vertex 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 7fef5997f13..868eb802747 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 @@ -158,7 +158,7 @@ Feature: Step - asNumber() g.inject(32768).asNumber(N.nshort) """ When iterated to list - Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + Then the traversal will raise an error with message containing text of "Can't convert number of type Integer to Short due to overflow." @GraphComputerVerificationInjectionNotSupported Scenario: g_injectX300X_asNumberXN_nbyteX @@ -168,7 +168,7 @@ Feature: Step - asNumber() g.inject(300).asNumber(N.nbyte) """ When iterated to list - Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + Then the traversal will raise an error with message containing text of "Can't convert number of type Integer to Byte due to overflow." @GraphComputerVerificationInjectionNotSupported Scenario: g_injectX5X_asNumberXX @@ -202,17 +202,7 @@ Feature: Step - asNumber() g.inject("1,000").asNumber(N.nint) """ When iterated to list - Then the traversal will raise an error with message containing text of "Can not parse number: '1,000'" - - @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX32768X_asNumberXN_nshortX - Given the empty graph - And the traversal of - """ - g.inject("32768").asNumber(N.nshort) - """ - When iterated to list - Then the traversal will raise an error with message containing text of "Can not convert number type as would cause overflow." + Then the traversal will raise an error with message containing text of "Can't parse string '1,000' as number." @GraphComputerVerificationInjectionNotSupported Scenario: g_injectXtestX_asNumberXX @@ -222,7 +212,7 @@ Feature: Step - asNumber() g.inject("test").asNumber() """ When iterated to list - Then the traversal will raise an error with message containing text of "Can not parse number: 'test'" + Then the traversal will raise an error with message containing text of "Can't parse string 'test' as number." @GraphComputerVerificationInjectionNotSupported Scenario: g_injectX_1__2__3__4_X_asNumberXX @@ -232,7 +222,7 @@ Feature: Step - asNumber() g.inject([1, 2, 3, 4]).asNumber() """ When iterated to list - Then the traversal will raise an error with message containing text of "Can't parse 'ArrayList' as number." + Then the traversal will raise an error with message containing text of "Can't parse type ArrayList as number." @GraphComputerVerificationInjectionNotSupported Scenario: g_injectX_1__2__3__4_X_unfoldXX_asNumberXX @@ -266,10 +256,7 @@ Feature: Step - asNumber() Given the empty graph And the traversal of """ - g.V(1).asNumber(N.nint) + g.inject(null).asNumber(N.nint) """ When iterated to list - Then the result should be unordered - | result | - | d[1].i | -# ==> Parsing Exception ("Type Vertex is not parsable to Type Integer") \ No newline at end of file + Then the traversal will raise an error with message containing text of "Can't parse type null as number." \ No newline at end of file From 01c2d78ec1939750e5c2a7fc711217a2f1a823f6 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Mon, 7 Jul 2025 17:55:22 -0400 Subject: [PATCH 3/9] 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 +++++++++++++++++++ .../traversal/step/map/AsNumberStep.java | 6 +-- .../traversal/step/map/AsNumberStepTest.java | 39 +++++++++++++++ .../Gherkin/Gremlin.cs | 2 +- gremlin-go/driver/cucumber/gremlin.go | 2 +- .../test/cucumber/gremlin.js | 2 +- .../src/main/python/radish/gremlin.py | 2 +- .../test/features/map/AsNumber.feature | 8 ++-- 10 files changed, 127 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 36d0d903440..040f96ca9bc 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -75,6 +75,7 @@ This release also includes changes from <>. * 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 eaf36d2b963..e39425881c1 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 c1de647afc6..50dea6654b4 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 1fe1a52051b..1fb093be118 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 static Number parseNumber(final String value) { 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 a812aac8270..396d4bd8e4c 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 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 @@ protected List getTraversals() { @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 25ba75311e7..e624af80f5a 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 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(32768).AsNumber(N.Nshort)}}, {"g_injectX300X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(300).AsNumber(N.Nbyte)}}, {"g_injectX5X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber()}}, - {"g_injectX5X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Nint)}}, + {"g_injectX5X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Nbyte)}}, {"g_injectX1_000X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("1,000").AsNumber(N.Nint)}}, {"g_injectXtestX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("test").AsNumber()}}, {"g_injectX_1__2__3__4_X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).AsNumber()}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 8612cdbf5e4..be4614c4907 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 5f75840cd2d..eaf50c7474a 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 8c2134f5926..89731ece254 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -609,7 +609,7 @@ '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 868eb802747..2a5972a0674 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 From 7ef100a5630103fe101c51e2b0c3da4e7cf95b63 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Thu, 17 Jul 2025 10:35:17 -0700 Subject: [PATCH 4/9] added comprehensive unit tests & updated asNumber() implementation --- docs/src/reference/the-traversal.asciidoc | 6 +- .../gremlin/process/traversal/N.java | 29 +- .../traversal/dsl/graph/GraphTraversal.java | 8 +- .../traversal/step/map/AsNumberStep.java | 105 +------ .../structure/io/gryo/GryoVersion.java | 17 +- .../tinkerpop/gremlin/util/NumberHelper.java | 75 +++++ .../traversal/step/map/AsNumberStepTest.java | 291 +++++++++++++++++- .../gremlin/util/NumberHelperTest.java | 75 +++++ .../test/features/map/AsNumber.feature | 2 +- 9 files changed, 476 insertions(+), 132 deletions(-) diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index e39425881c1..39dd9f9c39c 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -836,13 +836,13 @@ The `asNumber()`-step (*map*) converts the incoming traverser to the nearest par 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. +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. All other input types will result in `IllegalArgumentException`. [gremlin-groovy,modern] ---- -g.inject(1).asNumber() <1> +g.inject(1234).asNumber() <1> g.inject(1.76).asNumber() <2> g.inject(1.76).asNumber(N.nint) <3> g.inject("1b").asNumber() <4> @@ -851,7 +851,7 @@ 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. +<3> A double is converted into an int. <4> String containing any character other than numerical ones will result in `NumberFormatException`. <5> Narrowing of int to byte that overflows will throw `ArithmeticException`. diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java index 67eda53a7d9..4a8d56b48b7 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java @@ -20,26 +20,33 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.AsNumberStep; +import java.math.BigDecimal; +import java.math.BigInteger; + /** * Tokens that are used to denote different units of number. * Used with {@link AsNumberStep} step. */ public enum N { - nbyte("Byte"), - nshort("Short"), - nint("Integer"), - nlong("Long"), - nfloat("Float"), - ndouble("Double"), - nbigInt("BigInt"), - nbigDecimal("BigDecimal"),; + nbyte(Byte.class), + nshort(Short.class), + nint(Integer.class), + nlong(Long.class), + nfloat(Float.class), + ndouble(Double.class), + nbigInt(BigInteger.class), + nbigDecimal(BigDecimal.class),; + + private final Class type; - private final String typeName; + N(Class type) {this.type = type;} - N(String name) {typeName = name;} + public Class getType() { + return this.type; + } @Override public String toString() { - return typeName; + return this.type.getSimpleName(); } } \ No newline at end of file diff --git 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 index 40cda64a36d..aa3c2cfbc5a 100644 --- 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 @@ -1918,11 +1918,11 @@ public default GraphTraversal dateDiff(final Traversal dateTraver } /** - * Parse value of the incoming traverser as an ISO-8601 {@link Number}. + * Parse value of the incoming traverser as a {@link Number}. * * @return the traversal with an appended {@link AsNumberStep}. * @see Reference Documentation - AsNumberStep Step - * @since 3.7.1 + * @since 3.8.0 */ public default GraphTraversal asNumber() { this.asAdmin().getBytecode().addStep(Symbols.asNumber); @@ -1930,11 +1930,11 @@ public default GraphTraversal asNumber() { } /** - * Parse value of the incoming traverser as an ISO-8601 {@link Number}. + * Parse value of the incoming traverser as a {@link Number}. * * @return the traversal with an appended {@link AsNumberStep}. * @see Reference Documentation - AsNumberStep Step - * @since 3.7.1 + * @since 3.8.0 */ public default GraphTraversal asNumber(final N numberToken) { this.asAdmin().getBytecode().addStep(Symbols.asNumber, numberToken); 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 1fb093be118..59e88ef6047 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 @@ -26,19 +26,13 @@ import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import org.apache.tinkerpop.gremlin.util.NumberHelper; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.util.Calendar; import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; import java.util.Set; /** * Reference implementation for number parsing step. */ -public final class AsNumberStep extends ScalarMapStep { +public class AsNumberStep extends ScalarMapStep { private N numberToken; @@ -58,10 +52,10 @@ protected Number map(Traverser.Admin traverser) { if (object instanceof String) { String numberText = (String) object; Number number = parseNumber(numberText); - return numberToken == null ? autoNumber(number) : castNumber(number, numberToken); + return numberToken == null ? number : castNumber(number, numberToken); } else if (object instanceof Number) { Number number = (Number) object; - return numberToken == null ? autoNumber(number) : castNumber(number, numberToken); + return numberToken == null ? number : castNumber(number, numberToken); } throw new IllegalArgumentException(String.format("Can't parse type %s as number.", object == null ? "null" : object.getClass().getSimpleName())); } @@ -96,97 +90,14 @@ public String toString() { } public static Number parseNumber(final String value) { - try { - boolean isFloatingPoint = value.contains(".") || value.contains("e") || value.contains("E"); - if (isFloatingPoint) { - BigDecimal result = new BigDecimal(value.trim()); - if (BigDecimal.valueOf(result.doubleValue()).compareTo(result) == 0) { - return result.doubleValue(); - } - return result; - } - BigInteger result = new BigInteger(value.trim()); - 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(); - } - return result; - } catch (NumberFormatException nfe) { - throw new NumberFormatException(String.format("Can't parse string '%s' as number.", value)); + if (NumberUtils.isCreatable(value.trim())) { + return NumberUtils.createNumber(value.trim()); } + throw new NumberFormatException(String.format("Can't parse string '%s' as number.", value)); } - private static Number autoNumber(Number number) { - final Class clazz = number.getClass(); - if (clazz.equals(Float.class)) { - return castNumber(number, N.ndouble); - } - return number; - } - - private static Number castNumber(Number number, N numberToken) { - int sourceBits = getNumberBitsBasedOnValue(number); - int targetBits = getNumberTokenBits(numberToken); - if (sourceBits > targetBits) { - throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", - number.getClass().getSimpleName(), numberToken.toString())); - } - if (numberToken == N.nbyte) { - return number.byteValue(); - } else if (numberToken == N.nshort) { - return number.shortValue(); - } else if (numberToken == N.nint) { - return number.intValue(); - } else if (numberToken == N.nlong) { - return number.longValue(); - } else if (numberToken == N.nfloat) { - return number.floatValue(); - } else if (numberToken == N.ndouble) { - return number.doubleValue(); - } else if (numberToken == N.nbigInt) { - return BigInteger.valueOf(number.longValue()); - } else if (numberToken == N.nbigDecimal) { - return BigDecimal.valueOf(number.doubleValue()); - } - return number; + private static Number castNumber(final Number number, final N numberToken) { + return NumberHelper.castTo(number, numberToken); } - private static int getNumberBitsBasedOnValue(Number number) { - final Class clazz = number.getClass(); - if (clazz.equals(BigInteger.class)) { - return 128; - } else if (clazz.equals(BigDecimal.class)) { - return 128; - } - boolean floatingPoint = (clazz.equals(Float.class) || clazz.equals(Double.class)); - if (!floatingPoint && (number.longValue() >= Byte.MIN_VALUE) && (number.longValue() <= Byte.MAX_VALUE)) { - return 8; - } else if (!floatingPoint && (number.longValue() >= Short.MIN_VALUE) && (number.longValue() <= Short.MAX_VALUE)) { - return 16; - } else if (!floatingPoint && (number.longValue() >= Integer.MIN_VALUE) && (number.longValue() <= Integer.MAX_VALUE)) { - return 32; - } else if (floatingPoint && (number.doubleValue() >= Float.MIN_VALUE) && (number.doubleValue() <= Float.MAX_VALUE)) { - return 32; - } else { - return 64; - } - } - - private static int getNumberTokenBits(N numberToken) { - if (numberToken == N.nbyte) { - return 8; - } else if (numberToken == N.nshort) { - return 16; - } else if (numberToken == N.nint) { - return 32; - } else if (numberToken == N.nlong) { - return 64; - } else if (numberToken == N.nfloat) { - return 32; - } else if (numberToken == N.ndouble) { - return 64; - } - return 128; - } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java index 65b0f7164c9..d3e3a79d293 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoVersion.java @@ -25,20 +25,7 @@ import org.apache.tinkerpop.gremlin.process.computer.util.ComputerGraph; import org.apache.tinkerpop.gremlin.process.computer.util.MapMemory; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; -import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; -import org.apache.tinkerpop.gremlin.process.traversal.Contains; -import org.apache.tinkerpop.gremlin.process.traversal.DT; -import org.apache.tinkerpop.gremlin.process.traversal.Merge; -import org.apache.tinkerpop.gremlin.process.traversal.Operator; -import org.apache.tinkerpop.gremlin.process.traversal.Order; -import org.apache.tinkerpop.gremlin.process.traversal.P; -import org.apache.tinkerpop.gremlin.process.traversal.Path; -import org.apache.tinkerpop.gremlin.process.traversal.Pick; -import org.apache.tinkerpop.gremlin.process.traversal.Pop; -import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions; -import org.apache.tinkerpop.gremlin.process.traversal.Scope; -import org.apache.tinkerpop.gremlin.process.traversal.Text; -import org.apache.tinkerpop.gremlin.process.traversal.TextP; +import org.apache.tinkerpop.gremlin.process.traversal.*; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.RangeGlobalStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.FoldStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GroupCountStep; @@ -551,7 +538,7 @@ public static List> initV1Registrations() { add(GryoTypeReg.of(Pick.class, 137)); add(GryoTypeReg.of(DT.class, 198)); add(GryoTypeReg.of(Merge.class, 196)); - add(GryoTypeReg.of(DT.class, 199)); + add(GryoTypeReg.of(N.class, 200)); add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer())); add(GryoTypeReg.of(MultiComparator.class, 165)); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java index 78df426e9f0..137156fbcc4 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java @@ -18,6 +18,8 @@ */ package org.apache.tinkerpop.gremlin.util; +import org.apache.tinkerpop.gremlin.process.traversal.N; + import java.math.BigDecimal; import java.math.BigInteger; import java.math.MathContext; @@ -721,6 +723,79 @@ public static Number coerceTo(final Number a, final Class claz return a; } + /** + * Casts the given number to the specified numeric type if it can fit into it. + * Otherwise, throw. + * + * @param a the number to be cast + * @param numberToken the number token denoting the desired type to cast + * @return the number cast to the specified type + * @throws IllegalArgumentException if the specified numeric type is unsupported + * @throws ArithmeticException if the number overflows + */ + public static Number castTo(final Number a, final N numberToken) { + Class clazz = numberToken.getType(); + if (a.getClass().equals(clazz)) { + return a; + } else if (clazz.equals(Integer.class)) { + Long val = getLong(a, numberToken); + if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { + return a.intValue(); + } + } else if (clazz.equals(Long.class)) { + return getLong(a, numberToken); + } else if (clazz.equals(Float.class)) { + // BigDecimal to double will overflow into Infinity, we want to throw instead of passing through + if (!a.getClass().equals(BigDecimal.class) && + (Double.isInfinite(a.doubleValue()) || Double.isNaN(a.doubleValue()))) { + return a.floatValue(); + } + if (a.doubleValue() >= -Float.MAX_VALUE && a.doubleValue() <= Float.MAX_VALUE) { + return a.floatValue(); + } + } else if (clazz.equals(Double.class)) { + // BigDecimal to double will overflow into Infinity, we want to throw instead of passing through + if (!a.getClass().equals(BigDecimal.class) && + (Double.isInfinite(a.doubleValue()) || Double.isNaN(a.doubleValue()))) { + return a.doubleValue(); + } + if (!Double.isInfinite(a.doubleValue())) { + // float losses precision, use string intermediate + return a.getClass().equals(Float.class) ? Double.parseDouble(a.toString()) : a.doubleValue(); + } + } else if (clazz.equals(Byte.class)) { + Long val = getLong(a, numberToken); + if (val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE) { + return a.byteValue(); + } + } else if (clazz.equals(Short.class)) { + Long val = getLong(a, numberToken); + if (val >= Short.MIN_VALUE && val <= Short.MAX_VALUE) { + return a.shortValue(); + } + } else if (clazz.equals(BigInteger.class)) { + return NumberHelper.bigIntegerValue(a); + } else if (clazz.equals(BigDecimal.class)) { + // float losses precision, use string intermediate + return a.getClass().equals(Float.class) ? new BigDecimal(a.toString()) : NumberHelper.bigDecimalValue(a); + } else { + throw new IllegalArgumentException("Unsupported number type token: " + numberToken); + } + + throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", + a.getClass().getSimpleName(), numberToken)); + } + + private static Long getLong(Number num, N numberToken) { + try { + return num.getClass().equals(BigInteger.class) ? ((BigInteger) num).longValueExact() : num.longValue(); + } catch (ArithmeticException ae) { + throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", + num.getClass().getSimpleName(), numberToken)); + } + + } + private static NumberHelper getHelper(final Class clazz) { if (clazz.equals(Byte.class)) { return BYTE_NUMBER_HELPER; 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 396d4bd8e4c..eb1f9547d86 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 @@ -24,12 +24,13 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.StepTest; import org.junit.Test; +import java.math.BigDecimal; +import java.math.BigInteger; 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; public class AsNumberStepTest extends StepTest { @@ -46,9 +47,18 @@ public void testReturnTypes() { assertEquals(1, __.__(1.8).asNumber(N.nint).next()); assertEquals(1, __.__(1L).asNumber(N.nint).next()); assertEquals(1L, __.__(1L).asNumber().next()); + assertEquals(3.14f, __.__(3.14).asNumber(N.nfloat).next()); + assertEquals(3.14, __.__(3.14f).asNumber(N.ndouble).next()); assertEquals(1, __.__("1").asNumber(N.nint).next()); assertEquals(1, __.__("1").asNumber().next()); assertEquals((byte) 1, __.__("1").asNumber(N.nbyte).next()); + assertEquals((short) 1, __.__("1").asNumber(N.nshort).next()); + assertEquals(1L, __.__("1").asNumber(N.nlong).next()); + assertEquals(3.14, __.__("3.14").asNumber(N.ndouble).next()); //float to double + // NumberUtils allows additional string processing + assertEquals(123.0f, __.__("123.").asNumber().next()); + assertEquals(291, __.__("0x123").asNumber().next()); + assertEquals(83, __.__("0123").asNumber().next()); } @Test(expected = NumberFormatException.class) @@ -81,4 +91,283 @@ public void shouldThrowOverflowExceptionWhenCastNumberOverflows() { __.__(128).asNumber(N.nbyte).next(); } + @Test + public void testStringToByte() { + assertEquals((byte) 0, __.__("0").asNumber(N.nbyte).next()); + assertEquals((byte) 127, __.__("127").asNumber(N.nbyte).next()); + assertEquals((byte) -128, __.__("-128").asNumber(N.nbyte).next()); + assertEquals((byte) 42, __.__("42").asNumber(N.nbyte).next()); + assertEquals((byte) -42, __.__("-42").asNumber(N.nbyte).next()); + assertEquals((byte) 1, __.__("1").asNumber(N.nbyte).next()); + } + + @Test + public void testStringToShort() { + assertEquals((short) 0, __.__("0").asNumber(N.nshort).next()); + assertEquals((short) 32767, __.__("32767").asNumber(N.nshort).next()); + assertEquals((short) -32768, __.__("-32768").asNumber(N.nshort).next()); + assertEquals((short) 1000, __.__("1000").asNumber(N.nshort).next()); + assertEquals((short) -1000, __.__("-1000").asNumber(N.nshort).next()); + assertEquals((short) 255, __.__("255").asNumber(N.nshort).next()); + } + + @Test + public void testStringToInt() { + assertEquals(0, __.__("0").asNumber(N.nint).next()); + assertEquals(2147483647, __.__("2147483647").asNumber(N.nint).next()); + assertEquals(-2147483648, __.__("-2147483648").asNumber(N.nint).next()); + assertEquals(123456, __.__("123456").asNumber(N.nint).next()); + assertEquals(-123456, __.__("-123456").asNumber(N.nint).next()); + assertEquals(65536, __.__("65536").asNumber(N.nint).next()); + } + + @Test + public void testStringToLong() { + assertEquals(0L, __.__("0").asNumber(N.nlong).next()); + assertEquals(9223372036854775807L, __.__("9223372036854775807").asNumber(N.nlong).next()); + assertEquals(-9223372036854775808L, __.__("-9223372036854775808").asNumber(N.nlong).next()); + assertEquals(123456789L, __.__("123456789").asNumber(N.nlong).next()); + assertEquals(-123456789L, __.__("-123456789").asNumber(N.nlong).next()); + assertEquals(4294967296L, __.__("4294967296").asNumber(N.nlong).next()); + } + + @Test + public void testStringToFloat() { + assertEquals(0.0f, __.__("0.0").asNumber(N.nfloat).next()); + assertEquals(3.14f, __.__("3.14").asNumber(N.nfloat).next()); + assertEquals(-3.14f, __.__("-3.14").asNumber(N.nfloat).next()); + assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.nfloat).next()); + assertEquals(-1.23e-10f, __.__("-1.23e-10").asNumber(N.nfloat).next()); + assertEquals(Float.MAX_VALUE, __.__("3.4028235E38").asNumber(N.nfloat).next()); + assertEquals(Float.MIN_VALUE, __.__("1.4E-45").asNumber(N.nfloat).next()); + } + + @Test + public void testStringToDouble() { + assertEquals(0.0, __.__("0.0").asNumber(N.ndouble).next()); + assertEquals(3.141592653589793, __.__("3.141592653589793").asNumber(N.ndouble).next()); + assertEquals(-3.141592653589793, __.__("-3.141592653589793").asNumber(N.ndouble).next()); + assertEquals(1.23e100, __.__("1.23e100").asNumber(N.ndouble).next()); + assertEquals(-1.23e-100, __.__("-1.23e-100").asNumber(N.ndouble).next()); + assertEquals(Double.MAX_VALUE, __.__("1.7976931348623157E308").asNumber(N.ndouble).next()); + assertEquals(Double.MIN_VALUE, __.__("4.9E-324").asNumber(N.ndouble).next()); + } + + @Test + public void testStringToBigInteger() { + assertEquals(new BigInteger("0"), __.__("0").asNumber(N.nbigInt).next()); + assertEquals(new BigInteger("123456789012345678901234567890"), + __.__("123456789012345678901234567890").asNumber(N.nbigInt).next()); + assertEquals(new BigInteger("-123456789012345678901234567890"), + __.__("-123456789012345678901234567890").asNumber(N.nbigInt).next()); + assertEquals(new BigInteger("999999999999999999999999999999999999999999999999999"), + __.__("999999999999999999999999999999999999999999999999999").asNumber(N.nbigInt).next()); + assertEquals(new BigInteger("1"), __.__("1").asNumber(N.nbigInt).next()); + } + + @Test + public void testStringToBigDecimal() { + assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.nbigDecimal).next()); + assertEquals(new BigDecimal("123456789012345678901234567890.123456789"), + __.__("123456789012345678901234567890.123456789").asNumber(N.nbigDecimal).next()); + assertEquals(new BigDecimal("-123456789012345678901234567890.123456789"), + __.__("-123456789012345678901234567890.123456789").asNumber(N.nbigDecimal).next()); + // Note directly constructing BigDecimal returns 1E-39, but parsing then converting from double results in 1.0E-39 + assertEquals(BigDecimal.valueOf(Double.parseDouble("0.000000000000000000000000000000000000001")), + __.__("0.000000000000000000000000000000000000001").asNumber(N.nbigDecimal).next()); + assertEquals(new BigDecimal("1.0"), __.__("1.0").asNumber(N.nbigDecimal).next()); + } + +// ===== EDGE CASE TESTS ===== + + @Test + public void testCastInfinityAndNaN() { + assertEquals(Double.POSITIVE_INFINITY, __.__(Float.POSITIVE_INFINITY).asNumber(N.ndouble).next()); + assertEquals(Double.NEGATIVE_INFINITY, __.__(Float.NEGATIVE_INFINITY).asNumber(N.ndouble).next()); + assertEquals(Double.NaN, __.__(Float.NaN).asNumber(N.ndouble).next()); + assertEquals(Float.POSITIVE_INFINITY, __.__(Double.POSITIVE_INFINITY).asNumber(N.nfloat).next()); + assertEquals(Float.NEGATIVE_INFINITY, __.__(Double.NEGATIVE_INFINITY).asNumber(N.nfloat).next()); + assertEquals(Float.NaN, __.__(Double.NaN).asNumber(N.nfloat).next()); + assertEquals(Long.MAX_VALUE, __.__(Double.POSITIVE_INFINITY).asNumber(N.nlong).next()); + assertEquals(0, __.__(Double.NaN).asNumber(N.nint).next()); + } + + @Test + public void testStringWithWhitespace() { + assertEquals(42, __.__(" 42 ").asNumber(N.nint).next()); + assertEquals(42, __.__("\t42\n").asNumber(N.nint).next()); + assertEquals(3.14, __.__(" 3.14 ").asNumber(N.ndouble).next()); + assertEquals((byte) 127, __.__(" 127 ").asNumber(N.nbyte).next()); + } + + @Test + public void testStringWithPlusSign() { + assertEquals(42, __.__("+42").asNumber(N.nint).next()); + assertEquals(3.14, __.__("+3.14").asNumber(N.ndouble).next()); + assertEquals((byte) 42, __.__("+42").asNumber(N.nbyte).next()); + assertEquals(new BigInteger("42"), __.__("+42").asNumber(N.nbigInt).next()); + } + + @Test + public void testZeroValues() { + assertEquals((byte) 0, __.__("0").asNumber(N.nbyte).next()); + assertEquals((short) 0, __.__("0").asNumber(N.nshort).next()); + assertEquals(0, __.__("0").asNumber(N.nint).next()); + assertEquals(0L, __.__("0").asNumber(N.nlong).next()); + assertEquals(0.0f, __.__("0.0").asNumber(N.nfloat).next()); + assertEquals(0.0, __.__("0.0").asNumber(N.ndouble).next()); + assertEquals(new BigInteger("0"), __.__("0").asNumber(N.nbigInt).next()); + assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.nbigDecimal).next()); + } + + @Test + public void testScientificNotation() { + assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.nfloat).next()); + assertEquals(1.23e10, __.__("1.23e10").asNumber(N.ndouble).next()); + assertEquals(1.23e-10f, __.__("1.23e-10").asNumber(N.nfloat).next()); + assertEquals(1.23e-10, __.__("1.23e-10").asNumber(N.ndouble).next()); + assertEquals(new BigDecimal("1.23E10"), __.__("1.23e10").asNumber(N.nbigDecimal).next()); + } + +// ===== OVERFLOW EXCEPTION TESTS ===== + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringByteOverflowsPositive() { + __.__("128").asNumber(N.nbyte).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringByteOverflowsNegative() { + __.__("-129").asNumber(N.nbyte).next(); + + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringShortOverflowsPositive() { + __.__("32768").asNumber(N.nshort).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringShortOverflowsNegative() { + __.__("-32769").asNumber(N.nshort).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringIntOverflowsPositive() { + __.__("2147483648").asNumber(N.nint).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringIntOverflowsNegative() { + __.__("-2147483649").asNumber(N.nint).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringLongOverflowsPositive() { + __.__("9223372036854775809").asNumber(N.nlong).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenStringLongOverflowsNegative() { + __.__("-9223372036854775809").asNumber(N.nlong).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenDoubleStringToFloatOverflows() { + __.__("3.5E38").asNumber(N.nfloat).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenBigDecimalStringToDoubleOverflows() { + __.__("1.8E308").asNumber(N.ndouble).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenBigDecimalStringToFloatOverflows() { + __.__("1.8E308").asNumber(N.nfloat).next(); + } + +// ===== INVALID FORMAT EXCEPTION TESTS ===== + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenEmptyString() { + __.__("").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenWhitespaceOnlyString() { + __.__(" ").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenAlphabeticString() { + __.__("abc").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenMixedAlphanumericString() { + __.__("123abc456").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenSpecialCharactersString() { + __.__("12@34").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenMultipleDecimalPoints() { + __.__("12.34.56").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenMultipleSigns() { + __.__("++123").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenSignInMiddle() { + __.__("12+34").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenInvalidScientificNotation() { + __.__("12e").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenInvalidScientificNotationWithSign() { + __.__("12e+").asNumber().next(); + } + + @Test(expected = NumberFormatException.class) + public void shouldThrowExceptionWhenInvalidDecimalFormat() { + __.__(".").asNumber().next(); + } + +// ===== PRECISION AND BOUNDARY TESTS ===== + + @Test + public void testBoundaryValues() { + assertEquals(Byte.MAX_VALUE, __.__("127").asNumber(N.nbyte).next()); + assertEquals(Byte.MIN_VALUE, __.__("-128").asNumber(N.nbyte).next()); + assertEquals(Short.MAX_VALUE, __.__("32767").asNumber(N.nshort).next()); + assertEquals(Short.MIN_VALUE, __.__("-32768").asNumber(N.nshort).next()); + assertEquals(Integer.MAX_VALUE, __.__("2147483647").asNumber(N.nint).next()); + assertEquals(Integer.MIN_VALUE, __.__("-2147483648").asNumber(N.nint).next()); + assertEquals(Long.MAX_VALUE, __.__("9223372036854775807").asNumber(N.nlong).next()); + assertEquals(Long.MIN_VALUE, __.__("-9223372036854775808").asNumber(N.nlong).next()); + } + + @Test + public void testHighPrecisionDecimals() { + assertEquals(new BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067"), + __.__("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067").asNumber(N.nbigDecimal).next()); + } + + @Test + public void testVeryLargeNumbers() { + String largeNumber = "12345678901234567890123456789012345678901234567890123456789012345678901234567890"; + assertEquals(new BigInteger(largeNumber), __.__(largeNumber).asNumber(N.nbigInt).next()); + } + } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java index 314be66fcbc..ef88db95cee 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.util; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.javatuples.Quartet; import org.javatuples.Triplet; import org.junit.Test; @@ -709,4 +710,78 @@ public void shouldCoerceToConvertToFloatIfCanFit() { final Double value = 42.0; assertEquals(Float.valueOf(42.0f), NumberHelper.coerceTo(value, Float.class)); } + + + @Test + public void shouldCastToReturnSameInstanceForSameClass() { + final Integer value = 42; + assertEquals(value, NumberHelper.castTo(value, N.nint)); + } + + @Test + public void shouldCastToConvertToByte() { + final Integer value = 42; + assertEquals(Byte.valueOf((byte) 42), NumberHelper.castTo(value, N.nbyte)); + } + + @Test + public void shouldCastToConvertToShort() { + final Integer value = 42; + assertEquals(Short.valueOf((short) 42), NumberHelper.castTo(value, N.nbyte)); + } + + @Test + public void shouldCastToConvertToLong() { + final Integer value = 42; + assertEquals(Long.valueOf(42L), NumberHelper.castTo(value, N.nlong)); + } + + @Test + public void shouldCastToConvertToFloat() { + final Integer value = 42; + assertEquals(Float.valueOf(42.0f), NumberHelper.castTo(value, N.nfloat)); + } + + @Test + public void shouldCastToConvertToDouble() { + final Integer value = 42; + assertEquals(Double.valueOf(42.0), NumberHelper.castTo(value, N.ndouble)); + } + + @Test + public void shouldCastToConvertToBigInteger() { + final Integer value = 42; + assertEquals(BigInteger.valueOf(42), NumberHelper.castTo(value, N.nbigInt)); + } + + @Test + public void shouldCastToConvertToBigDecimal() { + final Integer value = 42; + assertEquals(BigDecimal.valueOf(42), NumberHelper.castTo(value, N.nbigDecimal)); + } + + @Test(expected = ArithmeticException.class) + public void shouldOverflowIfCannotFitInByte() { + final Integer value = 128; + NumberHelper.castTo(value, N.nbyte); + } + + @Test(expected = ArithmeticException.class) + public void shouldOverflowIfCannotFitInShort() { + final Integer value = 32768; + NumberHelper.castTo(value, N.nshort); + } + + @Test(expected = ArithmeticException.class) + public void shouldOverflowIfCannotFitInInteger() { + final Long value = 2147483648L; + NumberHelper.castTo(value, N.nint); + } + + @Test(expected = ArithmeticException.class) + public void shouldOverflowIfCannotFitInFloat() { + final Double value = Double.MAX_VALUE; + NumberHelper.castTo(value, N.nfloat); + } + } 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 2a5972a0674..70c2154841a 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 @@ -100,7 +100,7 @@ Feature: Step - asNumber() When iterated to list Then the result should be unordered | result | - | d[5.75].d | + | d[5.75].f | @GraphComputerVerificationInjectionNotSupported Scenario: g_injectX5_43X_asNumberXN_nintX From 66294ec8dd9c527a87673c566dd051bd1c7c3e80 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Thu, 17 Jul 2025 14:33:08 -0700 Subject: [PATCH 5/9] update to throw when casting infinity and nan to whole numbers --- .../apache/tinkerpop/gremlin/util/NumberHelper.java | 6 ++++++ .../traversal/step/map/AsNumberStepTest.java | 13 ++++++++++--- .../tinkerpop/gremlin/util/NumberHelperTest.java | 2 +- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java index 137156fbcc4..b31499a4adc 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java @@ -787,6 +787,12 @@ public static Number castTo(final Number a, final N numberToken) { } private static Long getLong(Number num, N numberToken) { + // Explicitly throw when converting floating point infinity and NaN to whole numbers + if (Double.isNaN(num.doubleValue())) { + throw new ArithmeticException(String.format("Can't convert NaN to %s.", numberToken)); + } else if (Double.isInfinite(num.doubleValue())) { + throw new ArithmeticException(String.format("Can't convert floating point infinity to %s.", numberToken)); + } try { return num.getClass().equals(BigInteger.class) ? ((BigInteger) num).longValueExact() : num.longValue(); } catch (ArithmeticException ae) { 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 eb1f9547d86..1eef3b829b4 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 @@ -188,8 +188,6 @@ public void testCastInfinityAndNaN() { assertEquals(Float.POSITIVE_INFINITY, __.__(Double.POSITIVE_INFINITY).asNumber(N.nfloat).next()); assertEquals(Float.NEGATIVE_INFINITY, __.__(Double.NEGATIVE_INFINITY).asNumber(N.nfloat).next()); assertEquals(Float.NaN, __.__(Double.NaN).asNumber(N.nfloat).next()); - assertEquals(Long.MAX_VALUE, __.__(Double.POSITIVE_INFINITY).asNumber(N.nlong).next()); - assertEquals(0, __.__(Double.NaN).asNumber(N.nint).next()); } @Test @@ -231,6 +229,16 @@ public void testScientificNotation() { // ===== OVERFLOW EXCEPTION TESTS ===== + @Test(expected = ArithmeticException.class) + public void shouldThrowCastExceptionConvertingInfinityToWholeNumber() { + __.__(Double.POSITIVE_INFINITY).asNumber(N.nlong).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowCastExceptionConvertingNaNToWholeNumber() { + __.__(Double.NaN).asNumber(N.nint).next(); + } + @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringByteOverflowsPositive() { __.__("128").asNumber(N.nbyte).next(); @@ -239,7 +247,6 @@ public void shouldThrowOverflowExceptionWhenStringByteOverflowsPositive() { @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringByteOverflowsNegative() { __.__("-129").asNumber(N.nbyte).next(); - } @Test(expected = ArithmeticException.class) diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java index ef88db95cee..20772da0c96 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java @@ -727,7 +727,7 @@ public void shouldCastToConvertToByte() { @Test public void shouldCastToConvertToShort() { final Integer value = 42; - assertEquals(Short.valueOf((short) 42), NumberHelper.castTo(value, N.nbyte)); + assertEquals(Short.valueOf((short) 42), NumberHelper.castTo(value, N.nshort)); } @Test From edd6f8161ae6b3e1ab82829b71365cc3ba6bd976 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Fri, 18 Jul 2025 10:21:25 -0700 Subject: [PATCH 6/9] feature test & docs update --- CHANGELOG.asciidoc | 2 +- .../dev/provider/gremlin-semantics.asciidoc | 27 +++++++ docs/src/reference/the-traversal.asciidoc | 2 +- docs/src/upgrade/release-3.8.x.asciidoc | 38 +++++++--- .../traversal/step/map/AsNumberStep.java | 4 +- .../tinkerpop/gremlin/util/NumberHelper.java | 5 +- .../Gherkin/Gremlin.cs | 27 +++---- gremlin-go/driver/cucumber/gremlin.go | 27 +++---- .../test/cucumber/gremlin.js | 27 +++---- .../src/main/python/radish/gremlin.py | 27 +++---- .../test/features/map/AsNumber.feature | 71 +++++++++++++++---- 11 files changed, 178 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 244b92f88c8..c969413f17d 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -76,7 +76,7 @@ This release also includes changes from <>. * 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. +* Added the `asNumber()` step 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/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc index a499a015fea..26aebcd241b 100644 --- a/docs/src/dev/provider/gremlin-semantics.asciidoc +++ b/docs/src/dev/provider/gremlin-semantics.asciidoc @@ -801,6 +801,33 @@ Incoming date remains unchanged. 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], link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asDate-step[reference] +[[asNumber-step]] +=== asNumber() + +*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. + +*Syntax:* `asNumber()` | `asNumber(N numberToken)` + +[width="100%",options="header"] +|========================================================= +|Start Step |Mid Step |Modulated |Domain |Range +|N |Y |N |`Number`/`String` |`Number` +|========================================================= + +*Arguments:* + +* `numberToken` - The enum `N` to denote the desired type to parse/cast to. + +If no type token is provided, the incoming number remains unchanged. + +*Exceptions* +* If any overflow occurs during narrowing of types, then an `ArithmeticException` will be thrown. +* If the incoming string cannot be parsed into a valid number format, then a `NumberFormatException` will be thrown. +* If the incoming traverser is a non-String/Number (including `null`) value then an `IllegalArgumentException` will be thrown. + +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], +link:https://tinkerpop.apache.org/docs/x.y.z/reference/#asNumber-step[reference] + [[barrier-step]] === barrier() diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 39dd9f9c39c..73cee7c5491 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -832,7 +832,7 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre [[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. +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. diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc index 2c49bd64101..2e04c587b22 100644 --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@ -32,25 +32,41 @@ complete list of all the modifications that are part of this release. ==== 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. +We have been iterative introducing new language features into Gremlin, with the last major set of 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. -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: +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 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.nint) +==>15 +---- + +Semantically, the `asNumber()` step will convert 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. `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(5.0).asNumber(N.nint) +==> 5 // casts double to int 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: 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 59e88ef6047..29ea0007e7b 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 @@ -47,7 +47,7 @@ public AsNumberStep(final Traversal.Admin traversal, final N numberToken) { } @Override - protected Number map(Traverser.Admin traverser) { + protected Number map(final Traverser.Admin traverser) { final Object object = traverser.get(); if (object instanceof String) { String numberText = (String) object; @@ -89,7 +89,7 @@ public String toString() { return StringFactory.stepString(this); } - public static Number parseNumber(final String value) { + private static Number parseNumber(final String value) { if (NumberUtils.isCreatable(value.trim())) { return NumberUtils.createNumber(value.trim()); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java index b31499a4adc..b9e6f8b7c5f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java @@ -786,11 +786,12 @@ public static Number castTo(final Number a, final N numberToken) { a.getClass().getSimpleName(), numberToken)); } - private static Long getLong(Number num, N numberToken) { + private static Long getLong(final Number num, final N numberToken) { // Explicitly throw when converting floating point infinity and NaN to whole numbers if (Double.isNaN(num.doubleValue())) { throw new ArithmeticException(String.format("Can't convert NaN to %s.", numberToken)); - } else if (Double.isInfinite(num.doubleValue())) { + } + if (Double.isInfinite(num.doubleValue())) { throw new ArithmeticException(String.format("Can't convert floating point infinity to %s.", numberToken)); } try { diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 7e7afe35f64..466499259a5 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -622,27 +622,30 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsDate()}}, {"g_injectXnullX_asDate", new List, ITraversal>> {(g,p) =>g.Inject(null).AsDate()}}, {"g_injectXinvalidstrX_asDate", new List, ITraversal>> {(g,p) =>g.Inject("This String is not an ISO 8601 Date").AsDate()}}, - {"g_injectX5bX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject((byte) 5).AsNumber()}}, - {"g_injectX5sX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject((short) 5).AsNumber()}}, - {"g_injectX5iX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber()}}, - {"g_injectX5lX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5l).AsNumber()}}, - {"g_injectX5nX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new BigInteger(5)).AsNumber()}}, - {"g_injectX5_0X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5.0).AsNumber()}}, - {"g_injectX5_75fX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(5.75f).AsNumber()}}, + {"g_injectX5bX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject((byte) 5).AsNumber()}}, + {"g_injectX5sX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject((short) 5).AsNumber()}}, + {"g_injectX5iX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber()}}, + {"g_injectX5lX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5l).AsNumber()}}, + {"g_injectX5nX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(new BigInteger(5)).AsNumber()}}, + {"g_injectX5_0X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5.0).AsNumber()}}, + {"g_injectX5_75fX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5.75f).AsNumber()}}, {"g_injectX5_43X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.43).AsNumber(N.Nint)}}, {"g_injectX5_67X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.67).AsNumber(N.Nint)}}, {"g_injectX5X_asNumberXN_nlongX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber(N.Nlong)}}, {"g_injectX12X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(12).AsNumber(N.Nbyte)}}, {"g_injectX32768X_asNumberXN_nshortX", new List, ITraversal>> {(g,p) =>g.Inject(32768).AsNumber(N.Nshort)}}, {"g_injectX300X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(300).AsNumber(N.Nbyte)}}, - {"g_injectX5X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber()}}, + {"g_injectX5X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber()}}, {"g_injectX5X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Nbyte)}}, {"g_injectX1_000X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("1,000").AsNumber(N.Nint)}}, - {"g_injectXtestX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject("test").AsNumber()}}, - {"g_injectX_1__2__3__4_X_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).AsNumber()}}, - {"g_injectX_1__2__3__4_X_unfoldXX_asNumberXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber()}}, - {"g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber().Fold()}}, + {"g_injectXtestX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject("test").AsNumber()}}, + {"g_injectX_1_2_3_4X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).AsNumber()}}, + {"g_injectX1_2_3_4X_unfold_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber()}}, + {"g_injectX_1__2__3__4_X_asNumberXX_foldXX", new List, ITraversal>> {(g,p) =>g.Inject("1", 2, "3", 4).AsNumber().Fold()}}, + {"g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(N.Nbyte)}}, {"g_VX1X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(null).AsNumber(N.Nint)}}, + {"g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX", new List, ITraversal>> {(g,p) =>g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(N.Nint)}}, + {"g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX", new List, ITraversal>> {(g,p) =>g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(N.Nlong)}}, {"g_injectX1_2X_asString", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString()}}, {"g_injectX1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString(Scope.Local)}}, {"g_injectXlist_1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsString(Scope.Local)}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 3af86f01654..9beb85fe5f3 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -592,27 +592,30 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_injectX1_2X_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).AsDate()}}, "g_injectXnullX_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsDate()}}, "g_injectXinvalidstrX_asDate": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("This String is not an ISO 8601 Date").AsDate()}}, - "g_injectX5bX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, - "g_injectX5sX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, - "g_injectX5iX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, - "g_injectX5lX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, - "g_injectX5nX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, - "g_injectX5_0X_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.0).AsNumber()}}, - "g_injectX5_75fX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.75).AsNumber()}}, + "g_injectX5bX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5sX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5iX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5lX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5nX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, + "g_injectX5_0X_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.0).AsNumber()}}, + "g_injectX5_75fX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.75).AsNumber()}}, "g_injectX5_43X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.43).AsNumber(gremlingo.N.Nint)}}, "g_injectX5_67X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.67).AsNumber(gremlingo.N.Nint)}}, "g_injectX5X_asNumberXN_nlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber(gremlingo.N.Nlong)}}, "g_injectX12X_asNumberXN_nbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(12).AsNumber(gremlingo.N.Nbyte)}}, "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_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("5").AsNumber()}}, "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()}}, - "g_injectX_1__2__3__4_X_unfoldXX_asNumberXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber()}}, - "g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber().Fold()}}, + "g_injectXtestX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("test").AsNumber()}}, + "g_injectX_1_2_3_4X_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).AsNumber()}}, + "g_injectX1_2_3_4X_unfold_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber()}}, + "g_injectX_1__2__3__4_X_asNumberXX_foldXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("1", 2, "3", 4).AsNumber().Fold()}}, + "g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(gremlingo.N.Nbyte)}}, "g_VX1X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsNumber(gremlingo.N.Nint)}}, + "g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(gremlingo.N.Nint)}}, + "g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(gremlingo.N.Nlong)}}, "g_injectX1_2X_asString": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString()}}, "g_injectX1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString(gremlingo.Scope.Local)}}, "g_injectXlist_1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).AsString(gremlingo.Scope.Local)}}, 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 4f021edbc1d..9ad1b33b8ea 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 @@ -623,27 +623,30 @@ const gremlins = { g_injectX1_2X_asDate: [function({g, xx1}) { return g.inject(xx1).asDate() }], g_injectXnullX_asDate: [function({g}) { return g.inject(null).asDate() }], g_injectXinvalidstrX_asDate: [function({g}) { return g.inject("This String is not an ISO 8601 Date").asDate() }], - g_injectX5bX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], - g_injectX5sX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], - g_injectX5iX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], - g_injectX5lX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], - g_injectX5nX_asNumberXX: [function({g}) { return g.inject(5).asNumber() }], - g_injectX5_0X_asNumberXX: [function({g}) { return g.inject(5.0).asNumber() }], - g_injectX5_75fX_asNumberXX: [function({g}) { return g.inject(5.75).asNumber() }], + g_injectX5bX_asNumber: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5sX_asNumber: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5iX_asNumber: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5lX_asNumber: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5nX_asNumber: [function({g}) { return g.inject(5).asNumber() }], + g_injectX5_0X_asNumber: [function({g}) { return g.inject(5.0).asNumber() }], + g_injectX5_75fX_asNumber: [function({g}) { return g.inject(5.75).asNumber() }], g_injectX5_43X_asNumberXN_nintX: [function({g}) { return g.inject(5.43).asNumber(N.nint) }], g_injectX5_67X_asNumberXN_nintX: [function({g}) { return g.inject(5.67).asNumber(N.nint) }], g_injectX5X_asNumberXN_nlongX: [function({g}) { return g.inject(5).asNumber(N.nlong) }], g_injectX12X_asNumberXN_nbyteX: [function({g}) { return g.inject(12).asNumber(N.nbyte) }], 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_asNumber: [function({g}) { return g.inject("5").asNumber() }], 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() }], - g_injectX_1__2__3__4_X_unfoldXX_asNumberXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber() }], - g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber().fold() }], + g_injectXtestX_asNumber: [function({g}) { return g.inject("test").asNumber() }], + g_injectX_1_2_3_4X_asNumber: [function({g}) { return g.inject([1, 2, 3, 4]).asNumber() }], + g_injectX1_2_3_4X_unfold_asNumber: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber() }], + g_injectX_1__2__3__4_X_asNumberXX_foldXX: [function({g}) { return g.inject("1", 2, "3", 4).asNumber().fold() }], + g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX: [function({g, xx1, xx3, xx2}) { return g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.nbyte) }], g_VX1X_asNumberXN_nintX: [function({g}) { return g.inject(null).asNumber(N.nint) }], + g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX: [function({g}) { return g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.nint) }], + g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX: [function({g, xx1}) { return g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.nlong) }], g_injectX1_2X_asString: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString() }], g_injectX1_2X_asStringXlocalX: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString(Scope.local) }], g_injectXlist_1_2X_asStringXlocalX: [function({g, xx1}) { return g.inject(xx1).asString(Scope.local) }], diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index ab2c6daeb7f..f33c343d580 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -595,27 +595,30 @@ 'g_injectX1_2X_asDate': [(lambda g, xx1=None:g.inject(xx1).as_date())], 'g_injectXnullX_asDate': [(lambda g:g.inject(None).as_date())], 'g_injectXinvalidstrX_asDate': [(lambda g:g.inject('This String is not an ISO 8601 Date').as_date())], - 'g_injectX5bX_asNumberXX': [(lambda g:g.inject(5).as_number())], - 'g_injectX5sX_asNumberXX': [(lambda g:g.inject(5).as_number())], - 'g_injectX5iX_asNumberXX': [(lambda g:g.inject(5).as_number())], - 'g_injectX5lX_asNumberXX': [(lambda g:g.inject(long(5)).as_number())], - 'g_injectX5nX_asNumberXX': [(lambda g:g.inject(bigint(5)).as_number())], - 'g_injectX5_0X_asNumberXX': [(lambda g:g.inject(5.0).as_number())], - 'g_injectX5_75fX_asNumberXX': [(lambda g:g.inject(5.75).as_number())], + 'g_injectX5bX_asNumber': [(lambda g:g.inject(5).as_number())], + 'g_injectX5sX_asNumber': [(lambda g:g.inject(5).as_number())], + 'g_injectX5iX_asNumber': [(lambda g:g.inject(5).as_number())], + 'g_injectX5lX_asNumber': [(lambda g:g.inject(long(5)).as_number())], + 'g_injectX5nX_asNumber': [(lambda g:g.inject(bigint(5)).as_number())], + 'g_injectX5_0X_asNumber': [(lambda g:g.inject(5.0).as_number())], + 'g_injectX5_75fX_asNumber': [(lambda g:g.inject(5.75).as_number())], 'g_injectX5_43X_asNumberXN_nintX': [(lambda g:g.inject(5.43).as_number(N.nint))], 'g_injectX5_67X_asNumberXN_nintX': [(lambda g:g.inject(5.67).as_number(N.nint))], 'g_injectX5X_asNumberXN_nlongX': [(lambda g:g.inject(5).as_number(N.nlong))], 'g_injectX12X_asNumberXN_nbyteX': [(lambda g:g.inject(12).as_number(N.nbyte))], '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_asNumber': [(lambda g:g.inject('5').as_number())], '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())], - 'g_injectX_1__2__3__4_X_unfoldXX_asNumberXX': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number())], - 'g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number().fold())], + 'g_injectXtestX_asNumber': [(lambda g:g.inject('test').as_number())], + 'g_injectX_1_2_3_4X_asNumber': [(lambda g:g.inject([1, 2, 3, 4]).as_number())], + 'g_injectX1_2_3_4X_unfold_asNumber': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number())], + 'g_injectX_1__2__3__4_X_asNumberXX_foldXX': [(lambda g:g.inject('1', 2, '3', 4).as_number().fold())], + 'g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX': [(lambda g, xx1=None,xx3=None,xx2=None:g.inject(xx1, xx2, xx3, '4', '0x5').as_number().sum_().as_number(N.nbyte))], 'g_VX1X_asNumberXN_nintX': [(lambda g:g.inject(None).as_number(N.nint))], + 'g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX': [(lambda g:g.V().as_('a').out('knows').as_('b').math('a + b').by('age').as_number(N.nint))], + 'g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX': [(lambda g, xx1=None:g.with_side_effect('x', xx1).V().values('age').math('_ + x').as_number(N.nlong))], 'g_injectX1_2X_asString': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string())], 'g_injectX1_2X_asStringXlocalX': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string(Scope.local))], 'g_injectXlist_1_2X_asStringXlocalX': [(lambda g, xx1=None:g.inject(xx1).as_string(Scope.local))], 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 70c2154841a..cb36ba87b67 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 @@ -19,7 +19,7 @@ Feature: Step - asNumber() @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5bX_asNumberXX + Scenario: g_injectX5bX_asNumber Given the empty graph And the traversal of """ @@ -31,7 +31,7 @@ Feature: Step - asNumber() | d[5].b | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5sX_asNumberXX + Scenario: g_injectX5sX_asNumber Given the empty graph And the traversal of """ @@ -43,7 +43,7 @@ Feature: Step - asNumber() | d[5].s | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5iX_asNumberXX + Scenario: g_injectX5iX_asNumber Given the empty graph And the traversal of """ @@ -55,7 +55,7 @@ Feature: Step - asNumber() | d[5].i | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5lX_asNumberXX + Scenario: g_injectX5lX_asNumber Given the empty graph And the traversal of """ @@ -67,7 +67,7 @@ Feature: Step - asNumber() | d[5].l | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5nX_asNumberXX + Scenario: g_injectX5nX_asNumber Given the empty graph And the traversal of """ @@ -79,7 +79,7 @@ Feature: Step - asNumber() | d[5].n | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5_0X_asNumberXX + Scenario: g_injectX5_0X_asNumber Given the empty graph And the traversal of """ @@ -91,7 +91,7 @@ Feature: Step - asNumber() | d[5].d | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5_75fX_asNumberXX + Scenario: g_injectX5_75fX_asNumber Given the empty graph And the traversal of """ @@ -171,7 +171,7 @@ Feature: Step - asNumber() Then the traversal will raise an error with message containing text of "Can't convert number of type Integer to Byte due to overflow." @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5X_asNumberXX + Scenario: g_injectX5X_asNumber Given the empty graph And the traversal of """ @@ -205,7 +205,7 @@ Feature: Step - asNumber() Then the traversal will raise an error with message containing text of "Can't parse string '1,000' as number." @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectXtestX_asNumberXX + Scenario: g_injectXtestX_asNumber Given the empty graph And the traversal of """ @@ -215,7 +215,7 @@ Feature: Step - asNumber() Then the traversal will raise an error with message containing text of "Can't parse string 'test' as number." @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX_1__2__3__4_X_asNumberXX + Scenario: g_injectX_1_2_3_4X_asNumber Given the empty graph And the traversal of """ @@ -225,7 +225,7 @@ Feature: Step - asNumber() Then the traversal will raise an error with message containing text of "Can't parse type ArrayList as number." @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX_1__2__3__4_X_unfoldXX_asNumberXX + Scenario: g_injectX1_2_3_4X_unfold_asNumber Given the empty graph And the traversal of """ @@ -240,17 +240,32 @@ Feature: Step - asNumber() | d[4].i | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX_1__2__3__4_X_unfoldXX_asNumberXX_foldXX + Scenario: g_injectX_1__2__3__4_X_asNumberXX_foldXX Given the empty graph And the traversal of """ - g.inject([1, 2, 3, 4]).unfold().asNumber().fold() + g.inject("1", 2, "3", 4).asNumber().fold() """ When iterated to list Then the result should be unordered | result | | l[d[1].i,d[2].i,d[3].i,d[4].i] | + @GraphComputerVerificationInjectionNotSupported + Scenario: g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX + Given the empty graph + And using the parameter xx1 defined as "d[1.0].d" + And using the parameter xx2 defined as "d[2].i" + And using the parameter xx3 defined as "d[3].l" + And the traversal of + """ + g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.nbyte) + """ + When iterated to list + Then the result should be unordered + | result | + | d[15].b | + @GraphComputerVerificationInjectionNotSupported Scenario: g_VX1X_asNumberXN_nintX Given the empty graph @@ -259,4 +274,32 @@ Feature: Step - asNumber() g.inject(null).asNumber(N.nint) """ When iterated to list - Then the traversal will raise an error with message containing text of "Can't parse type null as number." \ No newline at end of file + Then the traversal will raise an error with message containing text of "Can't parse type null as number." + + @GraphComputerVerificationReferenceOnly + Scenario: g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX + Given the modern graph + And the traversal of + """ + g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.nint) + """ + When iterated to list + Then the result should be unordered + | result | + | d[56].i | + | d[61].i | + + Scenario: g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX + Given the modern graph + And using the parameter xx1 defined as "d[100].i" + And the traversal of + """ + g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.nlong) + """ + When iterated to list + Then the result should be unordered + | result | + | d[129].l | + | d[127].l | + | d[132].l | + | d[135].l | From c3d5762841489d9b9ebfc31724017d78ebd59ecc Mon Sep 17 00:00:00 2001 From: xiazcy Date: Fri, 18 Jul 2025 15:38:10 -0700 Subject: [PATCH 7/9] nit docs update --- docs/src/reference/the-traversal.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 73cee7c5491..550a1af3d32 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -834,7 +834,7 @@ link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gre 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. +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. 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. From 0ae19cdac0fe5ca55394319c6626162a75e9c670 Mon Sep 17 00:00:00 2001 From: Yang Xia <55853655+xiazcy@users.noreply.github.com> Date: Mon, 28 Jul 2025 08:24:29 -0700 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: andreachild --- docs/src/upgrade/release-3.8.x.asciidoc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc index 2e04c587b22..31e9738afc5 100644 --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@ -32,13 +32,13 @@ complete list of all the modifications that are part of this release. ==== Number Conversion Step -We have been iterative introducing new language features into Gremlin, with the last major set of string, list and date manipulation +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 consistent format, making it easier to perform downstream mathematical operations. As an example: +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] ---- @@ -55,17 +55,17 @@ gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum().asNumber(N.nint) ==>15 ---- -Semantically, the `asNumber()` step will convert 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. +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 during narrowing of types: +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.nint) ==> 5 // casts double to int -gremlin> g.inject(12).asNumber(N.byte) +gremlin> g.inject(12).asNumber(N.nbyte) ==> 12 -gremlin> g.inject(128).asNumber(N.byte) +gremlin> g.inject(128).asNumber(N.nbyte) ==> ArithmeticException ---- @@ -75,7 +75,7 @@ String input will be parsed. By default, the smalled unit of number to be parsed ---- gremlin> g.inject("5").asNumber() ==> 5 -gremlin> g.inject("5.7").asNumber(N.int) +gremlin> g.inject("5.7").asNumber(N.nint) ==> 5 gremlin> g.inject("1,000").asNumber(N.nint) ==> NumberFormatException From 13a51e9dfb61b8856230899edae39b80e51b1ad8 Mon Sep 17 00:00:00 2001 From: xiazcy Date: Mon, 28 Jul 2025 19:38:29 -0700 Subject: [PATCH 9/9] handled overflow edge cases in bigint conversion and refactored coerceTo and castTo to use the same conversion logic in NumberHelper. Removed `n` from N enum value in Grammar, and used `_` suffix for keyword collisions in Java/Groovy/JS. --- docs/src/reference/the-traversal.asciidoc | 19 +- docs/src/upgrade/release-3.8.x.asciidoc | 14 +- .../grammar/GenericLiteralVisitor.java | 5 + .../language/grammar/TraversalEnumParser.java | 11 + .../grammar/TraversalMethodVisitor.java | 2 +- .../translator/GroovyTranslateVisitor.java | 9 + .../translator/JavaTranslateVisitor.java | 11 + .../JavascriptTranslateVisitor.java | 9 + .../gremlin/process/traversal/N.java | 26 +- .../io/binary/TypeSerializerRegistry.java | 49 +--- .../io/binary/types/EnumSerializer.java | 1 - .../io/binary/types/NSerializer.java | 46 ++++ .../structure/io/graphson/GraphSONModule.java | 8 +- .../io/graphson/TraversalSerializersV2.java | 40 ++- .../io/graphson/TraversalSerializersV3.java | 40 ++- .../tinkerpop/gremlin/util/NumberHelper.java | 88 +++---- .../grammar/GeneralLiteralVisitorTest.java | 38 ++- .../translator/GremlinTranslatorTest.java | 18 ++ .../traversal/step/map/AsNumberStepTest.java | 236 ++++++++++-------- .../gremlin/util/NumberHelperTest.java | 30 ++- .../src/Gremlin.Net/Process/Traversal/N.cs | 32 +-- .../Gherkin/Gremlin.cs | 24 +- gremlin-go/driver/cucumber/gremlin.go | 24 +- gremlin-go/driver/serializer.go | 2 +- gremlin-go/driver/traversal.go | 32 +-- .../lib/process/traversal.js | 10 +- .../test/cucumber/gremlin.js | 24 +- .../unit/graphbinary/AnySerializer-test.js | 6 +- .../unit/graphbinary/EnumSerializer-test.js | 1 + gremlin-language/src/main/antlr4/Gremlin.g4 | 16 +- .../language/corpus/DocumentationReader.java | 4 +- .../gremlin_python/process/traversal.py | 21 +- .../src/main/python/radish/gremlin.py | 24 +- .../driver/test_driver_remote_connection.py | 2 +- .../test/features/map/AsNumber.feature | 46 ++-- 35 files changed, 577 insertions(+), 391 deletions(-) create mode 100644 gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/NSerializer.java diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc index 550a1af3d32..6b587212f3a 100644 --- a/docs/src/reference/the-traversal.asciidoc +++ b/docs/src/reference/the-traversal.asciidoc @@ -844,9 +844,9 @@ All other input types will result in `IllegalArgumentException`. ---- g.inject(1234).asNumber() <1> g.inject(1.76).asNumber() <2> -g.inject(1.76).asNumber(N.nint) <3> +g.inject(1.76).asNumber(N.int_) <3> g.inject("1b").asNumber() <4> -g.inject(33550336).asNumber(N.nbyte) <5> +g.inject(33550336).asNumber(N.byte_) <5> ---- <1> An int will be passed through. @@ -855,6 +855,21 @@ g.inject(33550336).asNumber(N.nbyte) <5> <4> String containing any character other than numerical ones will result in `NumberFormatException`. <5> Narrowing of int to byte that overflows will throw `ArithmeticException`. +[NOTE, caption=Java] +==== +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_`. +==== + +[NOTE, caption=Groovy & Gremlin Console] +==== +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_`. +==== + +[NOTE, caption=JavaScript] +==== +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_`. +==== + *Additional References* link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#asNumber()++[`asNumber()`] diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc index 31e9738afc5..2ca45e6322d 100644 --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@ -51,7 +51,7 @@ 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.nint) +gremlin> g.inject(1.0, 2l, 3, "4", "0x5").asNumber().sum().asNumber(N.int_) ==>15 ---- @@ -61,11 +61,11 @@ Numerical input will pass through unless a type is specified by the number token [source,text] ---- -gremlin> g.inject(5.0).asNumber(N.nint) +gremlin> g.inject(5.0).asNumber(N.int_) ==> 5 // casts double to int -gremlin> g.inject(12).asNumber(N.nbyte) +gremlin> g.inject(12).asNumber(N.byte_) ==> 12 -gremlin> g.inject(128).asNumber(N.nbyte) +gremlin> g.inject(128).asNumber(N.byte_) ==> ArithmeticException ---- @@ -75,11 +75,11 @@ String input will be parsed. By default, the smalled unit of number to be parsed ---- gremlin> g.inject("5").asNumber() ==> 5 -gremlin> g.inject("5.7").asNumber(N.nint) +gremlin> g.inject("5.7").asNumber(N.int_) ==> 5 -gremlin> g.inject("1,000").asNumber(N.nint) +gremlin> g.inject("1,000").asNumber(N.int_) ==> NumberFormatException -gremlin> g.inject("128").asNumber(N.nbyte) +gremlin> g.inject("128").asNumber(N.byte_) ==> ArithmeticException ---- diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java index 2556f6ee87c..eb0b9dc343f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GenericLiteralVisitor.java @@ -601,6 +601,11 @@ public Object visitTraversalPick(final GremlinParser.TraversalPickContext ctx) { return TraversalEnumParser.parseTraversalEnumFromContext(Pick.class, ctx); } + @Override + public Object visitTraversalN(final GremlinParser.TraversalNContext ctx) { + return TraversalEnumParser.parseTraversalNFromContext(ctx); + } + @Override public Object visitTraversalStrategy(final GremlinParser.TraversalStrategyContext ctx) { return antlr.traversalStrategyVisitor.visitTraversalStrategy(ctx); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParser.java index 0c43f9ba492..e894f8bf242 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParser.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalEnumParser.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.language.grammar; import org.antlr.v4.runtime.tree.ParseTree; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.structure.Direction; @@ -65,4 +66,14 @@ public static Direction parseTraversalDirectionFromContext(final GremlinParser.T text = text.substring(Direction.class.getSimpleName().length() + 1); return Direction.directionValueOf(text); } + + /** + * Parsing of {@link N} requires some special handling because of java keyword collision. + */ + public static N parseTraversalNFromContext(final GremlinParser.TraversalNContext context) { + String text = context.getText(); + if (text.startsWith(N.class.getSimpleName())) + text = text.substring(N.class.getSimpleName().length() + 1); + return text.startsWith("big") ? N.valueOf(text) : N.valueOf(text + "_"); + } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java index 06044ef9dc7..2fc4434d3d8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java @@ -2117,7 +2117,7 @@ public GraphTraversal visitTraversalMethod_asNumber_Empty(final GremlinParser.Tr @Override public GraphTraversal visitTraversalMethod_asNumber_traversalN(final GremlinParser.TraversalMethod_asNumber_traversalNContext ctx) { return graphTraversal.asNumber( - TraversalEnumParser.parseTraversalEnumFromContext(N.class, ctx.traversalN())); + TraversalEnumParser.parseTraversalNFromContext(ctx.traversalN())); } public GraphTraversal[] getNestedTraversalList(final GremlinParser.NestedTraversalListContext ctx) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java index 97e76358ead..c5d83388d56 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/GroovyTranslateVisitor.java @@ -45,6 +45,15 @@ public GroovyTranslateVisitor(final String graphTraversalSourceName) { super(graphTraversalSourceName); } + @Override + public Void visitTraversalN(GremlinParser.TraversalNContext ctx) { + final String[] split = ctx.getText().split("\\."); + sb.append(processGremlinSymbol(split[0])).append("."); + sb.append(processGremlinSymbol(split[1])); + if (!split[1].startsWith("big")) sb.append("_"); + return null; + } + @Override public Void visitIntegerLiteral(final GremlinParser.IntegerLiteralContext ctx) { final String integerLiteral = ctx.getText().toLowerCase(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java index 81b6b25b840..18ae1bd05c8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/translator/JavaTranslateVisitor.java @@ -21,7 +21,9 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.OptionsStrategy; +import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceVertex; import org.apache.tinkerpop.gremlin.util.DatetimeHelper; @@ -106,6 +108,15 @@ public Void visitClassType(final GremlinParser.ClassTypeContext ctx) { return null; } + @Override + public Void visitTraversalN(GremlinParser.TraversalNContext ctx) { + final String[] split = ctx.getText().split("\\."); + sb.append(processGremlinSymbol(split[0])).append("."); + sb.append(processGremlinSymbol(split[1])); + if (!split[1].startsWith("big")) sb.append("_"); + return null; + } + @Override public Void visitGenericMapLiteral(final GremlinParser.GenericMapLiteralContext ctx) { sb.append("new LinkedHashMap() {{ "); 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 23d2bf7d80d..bd86a8ca43c 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 @@ -87,6 +87,15 @@ public Void visitConfiguration(final GremlinParser.ConfigurationContext ctx) { return null; } + @Override + public Void visitTraversalN(GremlinParser.TraversalNContext ctx) { + final String[] split = ctx.getText().split("\\."); + sb.append(processGremlinSymbol(split[0])).append("."); + sb.append(processGremlinSymbol(split[1])); + if (!split[1].startsWith("big")) sb.append("_"); + return null; + } + @Override public Void visitGenericMapLiteral(final GremlinParser.GenericMapLiteralContext ctx) { sb.append("new Map(["); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java index 4a8d56b48b7..7ac673b4c1f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/N.java @@ -28,25 +28,21 @@ * Used with {@link AsNumberStep} step. */ public enum N { - nbyte(Byte.class), - nshort(Short.class), - nint(Integer.class), - nlong(Long.class), - nfloat(Float.class), - ndouble(Double.class), - nbigInt(BigInteger.class), - nbigDecimal(BigDecimal.class),; + byte_(Byte.class), + short_(Short.class), + int_(Integer.class), + long_(Long.class), + float_(Float.class), + double_(Double.class), + bigInt(BigInteger.class), + bigDecimal(BigDecimal.class),; - private final Class type; + private final Class type; - N(Class type) {this.type = type;} + N(Class type) {this.type = type;} - public Class getType() { + public Class getType() { return this.type; } - @Override - public String toString() { - return this.type.getSimpleName(); - } } \ No newline at end of file diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java index 4b48d8d2112..62597d16d9d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java @@ -49,52 +49,7 @@ import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; import org.apache.tinkerpop.gremlin.structure.io.IoRegistry; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.BigDecimalSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.BigIntegerSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.BindingSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.BulkSetSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ByteBufferSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ByteCodeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.CharSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ClassSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.CustomTypeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.DateSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.DurationSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.EdgeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.EnumSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.GraphSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.InetAddressSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.InstantSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.LambdaSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ListSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.LocalDateSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.LocalDateTimeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.LocalTimeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.MapEntrySerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.MapSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.MetricsSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.MonthDaySerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.OffsetDateTimeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.OffsetTimeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.PSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.PathSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.PeriodSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.PropertySerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.SetSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.SingleTypeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.StringSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TransformSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TraversalExplanationSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TraversalMetricsSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TraversalStrategySerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TraverserSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.TreeSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.UUIDSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.VertexPropertySerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.VertexSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.YearMonthSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ZoneOffsetSerializer; -import org.apache.tinkerpop.gremlin.structure.io.binary.types.ZonedDateTimeSerializer; +import org.apache.tinkerpop.gremlin.structure.io.binary.types.*; import org.apache.tinkerpop.gremlin.util.function.Lambda; import org.javatuples.Pair; @@ -167,7 +122,7 @@ public static Builder build() { new RegistryEntry<>(Direction.class, EnumSerializer.DirectionSerializer), new RegistryEntry<>(DT.class, EnumSerializer.DTSerializer), new RegistryEntry<>(Merge.class, EnumSerializer.MergeSerializer), - new RegistryEntry<>(N.class, EnumSerializer.NSerializer), + new RegistryEntry<>(N.class, new NSerializer()), new RegistryEntry<>(Operator.class, EnumSerializer.OperatorSerializer), new RegistryEntry<>(Order.class, EnumSerializer.OrderSerializer), new RegistryEntry<>(Pick.class, EnumSerializer.PickSerializer), diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java index 730ffab1c92..7ee0dd0406f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/EnumSerializer.java @@ -52,7 +52,6 @@ public class EnumSerializer extends SimpleTypeSerializer { public static final EnumSerializer DirectionSerializer = new EnumSerializer<>(DataType.DIRECTION, Direction::valueOf); public static final EnumSerializer
DTSerializer = new EnumSerializer<>(DataType.DT, DT::valueOf); public static final EnumSerializer MergeSerializer = new EnumSerializer<>(DataType.MERGE, Merge::valueOf); - public static final EnumSerializer NSerializer = new EnumSerializer<>(DataType.N, N::valueOf); public static final EnumSerializer OperatorSerializer = new EnumSerializer<>(DataType.OPERATOR, Operator::valueOf); public static final EnumSerializer OrderSerializer = new EnumSerializer<>(DataType.ORDER, Order::valueOf); public static final EnumSerializer PickSerializer = new EnumSerializer<>(DataType.PICK, Pick::valueOf); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/NSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/NSerializer.java new file mode 100644 index 00000000000..b70388ab6fa --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/NSerializer.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.tinkerpop.gremlin.structure.io.binary.types; + +import org.apache.tinkerpop.gremlin.process.traversal.N; +import org.apache.tinkerpop.gremlin.structure.io.Buffer; +import org.apache.tinkerpop.gremlin.structure.io.binary.DataType; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; +import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; + +import java.io.IOException; +import java.time.MonthDay; + +public class NSerializer extends SimpleTypeSerializer { + public NSerializer() { + super(DataType.N); + } + + @Override + protected N readValue(final Buffer buffer, final GraphBinaryReader context) throws IOException { + String name = context.read(buffer); + return name.startsWith("big") ? N.valueOf(name) : N.valueOf(name + "_"); + } + + @Override + protected void writeValue(final N value, final Buffer buffer, final GraphBinaryWriter context) throws IOException { + String name = value.name().replace("_", ""); + context.write(name, buffer); + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java index 1869f3d3b1a..b40d029a980 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java @@ -279,7 +279,6 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Direction.class, DT.class, Merge.class, - N.class, Operator.class, Order.class, Pop.class, @@ -287,6 +286,7 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Scope.class, Pick.class, T.class).forEach(e -> addSerializer(e, new TraversalSerializersV3.EnumJacksonSerializer())); + addSerializer(N.class, new TraversalSerializersV3.NJacksonSerializer()); addSerializer(P.class, new TraversalSerializersV3.PJacksonSerializer()); addSerializer(Lambda.class, new TraversalSerializersV3.LambdaJacksonSerializer()); addSerializer(Bytecode.Binding.class, new TraversalSerializersV3.BindingJacksonSerializer()); @@ -327,7 +327,6 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Direction.values(), DT.values(), Merge.values(), - N.values(), Operator.values(), Order.values(), Pop.values(), @@ -335,6 +334,7 @@ protected GraphSONModuleV3(final boolean normalize, final TypeInfo typeInfo) { Scope.values(), Pick.values(), T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV3.EnumJacksonDeserializer(e.getDeclaringClass()))); + addDeserializer(N.class, new TraversalSerializersV3.NJacksonDeserializer()); addDeserializer(P.class, new TraversalSerializersV3.PJacksonDeserializer()); addDeserializer(TextP.class, new TraversalSerializersV3.TextPJacksonDeserializer()); addDeserializer(Lambda.class, new TraversalSerializersV3.LambdaJacksonDeserializer()); @@ -536,7 +536,6 @@ protected GraphSONModuleV2(final boolean normalize) { Direction.class, DT.class, Merge.class, - N.class, Operator.class, Order.class, Pop.class, @@ -544,6 +543,7 @@ protected GraphSONModuleV2(final boolean normalize) { Scope.class, Pick.class, T.class).forEach(e -> addSerializer(e, new TraversalSerializersV2.EnumJacksonSerializer())); + addSerializer(N.class, new TraversalSerializersV2.NJacksonSerializer()); addSerializer(P.class, new TraversalSerializersV2.PJacksonSerializer()); addSerializer(Lambda.class, new TraversalSerializersV2.LambdaJacksonSerializer()); addSerializer(Bytecode.Binding.class, new TraversalSerializersV2.BindingJacksonSerializer()); @@ -576,7 +576,6 @@ protected GraphSONModuleV2(final boolean normalize) { Direction.values(), DT.values(), Merge.values(), - N.values(), Operator.values(), Order.values(), Pop.values(), @@ -584,6 +583,7 @@ protected GraphSONModuleV2(final boolean normalize) { Scope.values(), Pick.values(), T.values()).flatMap(Stream::of).forEach(e -> addDeserializer(e.getClass(), new TraversalSerializersV2.EnumJacksonDeserializer(e.getDeclaringClass()))); + addDeserializer(N.class, new TraversalSerializersV2.NJacksonDeserializer()); addDeserializer(P.class, new TraversalSerializersV2.PJacksonDeserializer()); addDeserializer(TextP.class, new TraversalSerializersV2.TextPJacksonDeserializer()); addDeserializer(Lambda.class, new TraversalSerializersV2.LambdaJacksonDeserializer()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java index 2ef49eaa339..4a234098b69 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java @@ -22,12 +22,7 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; -import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; -import org.apache.tinkerpop.gremlin.process.traversal.P; -import org.apache.tinkerpop.gremlin.process.traversal.TextP; -import org.apache.tinkerpop.gremlin.process.traversal.Traversal; -import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; -import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.*; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; @@ -136,6 +131,21 @@ public void serialize(final Enum enumInstance, final JsonGenerator jsonGenerator } + static class NJacksonSerializer extends StdScalarSerializer { + + public NJacksonSerializer() { + super(N.class); + } + + @Override + public void serialize(final N enumInstance, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) + throws IOException { + String name = enumInstance.name(); + jsonGenerator.writeString(name.replace("_", "")); + } + + } + final static class PJacksonSerializer extends StdScalarSerializer

{ public PJacksonSerializer() { @@ -316,6 +326,24 @@ public boolean isCachable() { } } + final static class NJacksonDeserializer extends StdDeserializer { + + public NJacksonDeserializer() { + super(N.class); + } + + @Override + public N deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final String enumName = jsonParser.getText(); + return N.valueOf(enumName.startsWith("big") ? enumName : enumName + "_"); + } + + @Override + public boolean isCachable() { + return true; + } + } + final static class PJacksonDeserializer extends StdDeserializer

{ public PJacksonDeserializer() { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java index 9bd1002ac79..3b234de3757 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java @@ -22,12 +22,7 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; -import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; -import org.apache.tinkerpop.gremlin.process.traversal.P; -import org.apache.tinkerpop.gremlin.process.traversal.TextP; -import org.apache.tinkerpop.gremlin.process.traversal.Traversal; -import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; -import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.*; import org.apache.tinkerpop.gremlin.process.traversal.step.util.BulkSet; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; @@ -143,6 +138,21 @@ public void serialize(final Enum enumInstance, final JsonGenerator jsonGenerator } + static class NJacksonSerializer extends StdScalarSerializer { + + public NJacksonSerializer() { + super(N.class); + } + + @Override + public void serialize(final N enumInstance, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) + throws IOException { + String name = enumInstance.name(); + jsonGenerator.writeString(name.replace("_", "")); + } + + } + final static class PJacksonSerializer extends StdScalarSerializer

{ public PJacksonSerializer() { @@ -336,6 +346,24 @@ public boolean isCachable() { } } + final static class NJacksonDeserializer extends StdDeserializer { + + public NJacksonDeserializer() { + super(N.class); + } + + @Override + public N deserialize(final JsonParser jsonParser, final DeserializationContext deserializationContext) throws IOException, JsonProcessingException { + final String enumName = jsonParser.getText(); + return N.valueOf(enumName.startsWith("big") ? enumName : enumName + "_"); + } + + @Override + public boolean isCachable() { + return true; + } + } + final static class PJacksonDeserializer extends AbstractReflectJacksonDeserializer

{ public PJacksonDeserializer() { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java index b9e6f8b7c5f..41beeaa392b 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/NumberHelper.java @@ -689,38 +689,12 @@ public static Integer compare(final Number a, final Number b) { * @throws IllegalArgumentException if the specified numeric type is unsupported */ public static Number coerceTo(final Number a, final Class clazz) { - if (a.getClass().equals(clazz)) { + try { + return performConversion(a, clazz); + } catch (ArithmeticException e) { + // return as-is since it didn't fit the type we wanted to coerce to return a; - } else if (clazz.equals(Integer.class)) { - if (a.longValue() >= Integer.MIN_VALUE && a.longValue() <= Integer.MAX_VALUE) { - return a.intValue(); - } - } else if (clazz.equals(Long.class)) { - return a.longValue(); - } else if (clazz.equals(Float.class)) { - if (a.doubleValue() >= -Float.MAX_VALUE && a.doubleValue() <= Float.MAX_VALUE) { - return a.floatValue(); - } - } else if (clazz.equals(Double.class)) { - return a.doubleValue(); - } else if (clazz.equals(Byte.class)) { - if (a.longValue() >= Byte.MIN_VALUE && a.longValue() <= Byte.MAX_VALUE) { - return a.byteValue(); - } - } else if (clazz.equals(Short.class)) { - if (a.longValue() >= Short.MIN_VALUE && a.longValue() <= Short.MAX_VALUE) { - return a.shortValue(); - } - } else if (clazz.equals(BigInteger.class)) { - return NumberHelper.bigIntegerValue(a); - } else if (clazz.equals(BigDecimal.class)) { - return NumberHelper.bigDecimalValue(a); - } else { - throw new IllegalArgumentException("Unsupported numeric type: " + clazz); } - - // return as-is since it didn't fit the type we wanted to coerce to - return a; } /** @@ -734,27 +708,36 @@ public static Number coerceTo(final Number a, final Class claz * @throws ArithmeticException if the number overflows */ public static Number castTo(final Number a, final N numberToken) { - Class clazz = numberToken.getType(); + Class clazz = numberToken.getType(); + return performConversion(a, clazz); + } + + /** + * Core conversion logic. + * Throws ArithmeticException when conversion would overflow. + */ + private static Number performConversion(final Number a, final Class clazz) { if (a.getClass().equals(clazz)) { return a; - } else if (clazz.equals(Integer.class)) { - Long val = getLong(a, numberToken); + } + if (clazz.equals(Integer.class)) { + Long val = getLong(a, clazz); if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) { return a.intValue(); } } else if (clazz.equals(Long.class)) { - return getLong(a, numberToken); + return getLong(a, clazz); } else if (clazz.equals(Float.class)) { - // BigDecimal to double will overflow into Infinity, we want to throw instead of passing through + // BigDecimal to double will overflow into Infinity, we want to handle this if (!a.getClass().equals(BigDecimal.class) && - (Double.isInfinite(a.doubleValue()) || Double.isNaN(a.doubleValue()))) { + (Double.isInfinite(a.doubleValue()) || Double.isNaN(a.doubleValue()))) { return a.floatValue(); } if (a.doubleValue() >= -Float.MAX_VALUE && a.doubleValue() <= Float.MAX_VALUE) { return a.floatValue(); } } else if (clazz.equals(Double.class)) { - // BigDecimal to double will overflow into Infinity, we want to throw instead of passing through + // BigDecimal to double will overflow into Infinity, we want to handle this if (!a.getClass().equals(BigDecimal.class) && (Double.isInfinite(a.doubleValue()) || Double.isNaN(a.doubleValue()))) { return a.doubleValue(); @@ -764,41 +747,45 @@ public static Number castTo(final Number a, final N numberToken) { return a.getClass().equals(Float.class) ? Double.parseDouble(a.toString()) : a.doubleValue(); } } else if (clazz.equals(Byte.class)) { - Long val = getLong(a, numberToken); + Long val = getLong(a, clazz); if (val >= Byte.MIN_VALUE && val <= Byte.MAX_VALUE) { return a.byteValue(); } } else if (clazz.equals(Short.class)) { - Long val = getLong(a, numberToken); + Long val = getLong(a, clazz); if (val >= Short.MIN_VALUE && val <= Short.MAX_VALUE) { return a.shortValue(); } } else if (clazz.equals(BigInteger.class)) { return NumberHelper.bigIntegerValue(a); } else if (clazz.equals(BigDecimal.class)) { - // float losses precision, use string intermediate - return a.getClass().equals(Float.class) ? new BigDecimal(a.toString()) : NumberHelper.bigDecimalValue(a); + return NumberHelper.bigDecimalValue(a); } else { - throw new IllegalArgumentException("Unsupported number type token: " + numberToken); + throw new IllegalArgumentException("Unsupported numeric type: " + clazz); } throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", - a.getClass().getSimpleName(), numberToken)); + a.getClass().getSimpleName(), clazz.getSimpleName())); } - private static Long getLong(final Number num, final N numberToken) { + private static Long getLong(final Number num, final Class clazz) { // Explicitly throw when converting floating point infinity and NaN to whole numbers if (Double.isNaN(num.doubleValue())) { - throw new ArithmeticException(String.format("Can't convert NaN to %s.", numberToken)); + throw new ArithmeticException(String.format("Can't convert NaN to %s.", clazz.getSimpleName())); } if (Double.isInfinite(num.doubleValue())) { - throw new ArithmeticException(String.format("Can't convert floating point infinity to %s.", numberToken)); + throw new ArithmeticException(String.format("Can't convert floating point infinity to %s.", clazz.getSimpleName())); + } + String msg = String.format("Can't convert number of type %s to %s due to overflow.", + num.getClass().getSimpleName(), clazz.getSimpleName()); + if ((num.getClass().equals(Double.class) || num.getClass().equals(Float.class)) && (num.doubleValue()) > Long.MAX_VALUE) { + throw new ArithmeticException(msg); } try { + if (num.getClass().equals(BigDecimal.class)) return ((BigDecimal) num).longValueExact(); return num.getClass().equals(BigInteger.class) ? ((BigInteger) num).longValueExact() : num.longValue(); } catch (ArithmeticException ae) { - throw new ArithmeticException(String.format("Can't convert number of type %s to %s due to overflow.", - num.getClass().getSimpleName(), numberToken)); + throw new ArithmeticException(msg); } } @@ -834,6 +821,8 @@ private static NumberHelper getHelper(final Class clazz) { private static BigInteger bigIntegerValue(final Number number) { if (number == null) return null; if (number instanceof BigInteger) return (BigInteger) number; + if (number instanceof BigDecimal) return ((BigDecimal) number).toBigInteger(); + if (number instanceof Double) return BigDecimal.valueOf(number.doubleValue()).toBigInteger(); return BigInteger.valueOf(number.longValue()); } @@ -841,7 +830,8 @@ private static BigDecimal bigDecimalValue(final Number number) { if (number == null) return null; if (number instanceof BigDecimal) return (BigDecimal) number; if (number instanceof BigInteger) return new BigDecimal((BigInteger) number); - return (number instanceof Double || number instanceof Float) + if (number instanceof Float) return new BigDecimal(number.toString()); // float losses precision, use string intermediate + return (number instanceof Double) ? BigDecimal.valueOf(number.doubleValue()) : BigDecimal.valueOf(number.longValue()); } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java index 2409663e478..03165106a95 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GeneralLiteralVisitorTest.java @@ -20,6 +20,7 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import org.apache.tinkerpop.gremlin.process.traversal.N; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.T; import org.apache.tinkerpop.gremlin.structure.VertexProperty; @@ -31,15 +32,12 @@ import org.junit.runners.Parameterized; import java.lang.reflect.Constructor; -import java.math.BigDecimal; import java.math.BigInteger; import java.time.LocalDateTime; import java.time.OffsetDateTime; import java.time.ZoneOffset; -import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -459,6 +457,40 @@ public void shouldParse() throws Exception { } } + @RunWith(Parameterized.class) + public static class ValidEnumNLiteralTest { + + @Parameterized.Parameter(value = 0) + public String script; + + @Parameterized.Parameter(value = 1) + public N expected; + + @Parameterized.Parameters(name = "{0}") + public static Iterable generateTestParameters() { + return Arrays.asList(new Object[][]{ + {"byte", N.byte_}, + {"short", N.short_}, + {"int", N.int_}, + {"long", N.long_}, + {"float", N.float_}, + {"double", N.double_}, + {"bigInt", N.bigInt}, + {"bigDecimal", N.bigDecimal}, + }); + } + + @Test + public void shouldParse() { + final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script)); + final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer)); + final GremlinParser.TraversalNContext ctx = parser.traversalN(); + + final N n = (N) new GenericLiteralVisitor(new GremlinAntlrToJava()).visitTraversalN(ctx); + assertEquals(expected, n); + } + } + @RunWith(Parameterized.class) public static class ValidDatetimeLiteralTest { 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 746a0cd711d..0b47507c464 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 @@ -740,6 +740,24 @@ public static Collection data() { null, null, "g.inject(long(1694017707000)).as_date()"}, + {"g.inject(\"123.4\").asNumber(N.int)", + null, + "g.inject(string0).asNumber(N.int)", + "g.Inject(\"123.4\").AsNumber(N.Int)", + "g.Inject(\"123.4\").AsNumber(gremlingo.N.Int)", + "g.inject(\"123.4\").asNumber(N.int_)", + "g.inject(\"123.4\").asNumber(N.int_)", + "g.inject(\"123.4\").asNumber(N.int_)", + "g.inject('123.4').as_number(N.int)"}, + {"g.inject(\"123.4\").asNumber(N.bigInt)", + null, + "g.inject(string0).asNumber(N.bigInt)", + "g.Inject(\"123.4\").AsNumber(N.BigInt)", + "g.Inject(\"123.4\").AsNumber(gremlingo.N.BigInt)", + "g.inject(\"123.4\").asNumber(N.bigInt)", + "g.inject(\"123.4\").asNumber(N.bigInt)", + "g.inject(\"123.4\").asNumber(N.bigInt)", + "g.inject('123.4').as_number(N.big_int)"}, {"g.V().hasLabel(null)", null, "g.V().hasLabel(string0)", 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 1eef3b829b4..ec98bd02851 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 @@ -43,18 +43,18 @@ protected List getTraversals() { @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((byte) 1, __.__(1).asNumber(N.byte_).next()); + assertEquals(1, __.__(1.8).asNumber(N.int_).next()); + assertEquals(1, __.__(1L).asNumber(N.int_).next()); assertEquals(1L, __.__(1L).asNumber().next()); - assertEquals(3.14f, __.__(3.14).asNumber(N.nfloat).next()); - assertEquals(3.14, __.__(3.14f).asNumber(N.ndouble).next()); - assertEquals(1, __.__("1").asNumber(N.nint).next()); + assertEquals(3.14f, __.__(3.14).asNumber(N.float_).next()); + assertEquals(3.14, __.__(3.14f).asNumber(N.double_).next()); + assertEquals(1, __.__("1").asNumber(N.int_).next()); assertEquals(1, __.__("1").asNumber().next()); - assertEquals((byte) 1, __.__("1").asNumber(N.nbyte).next()); - assertEquals((short) 1, __.__("1").asNumber(N.nshort).next()); - assertEquals(1L, __.__("1").asNumber(N.nlong).next()); - assertEquals(3.14, __.__("3.14").asNumber(N.ndouble).next()); //float to double + assertEquals((byte) 1, __.__("1").asNumber(N.byte_).next()); + assertEquals((short) 1, __.__("1").asNumber(N.short_).next()); + assertEquals(1L, __.__("1").asNumber(N.long_).next()); + assertEquals(3.14, __.__("3.14").asNumber(N.double_).next()); //float to double // NumberUtils allows additional string processing assertEquals(123.0f, __.__("123.").asNumber().next()); assertEquals(291, __.__("0x123").asNumber().next()); @@ -83,215 +83,231 @@ public void shouldThrowExceptionWhenUUIDInput() { @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenParsedNumberOverflows() { - __.__("128").asNumber(N.nbyte).next(); + __.__("128").asNumber(N.byte_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenCastNumberOverflows() { - __.__(128).asNumber(N.nbyte).next(); + __.__(128).asNumber(N.byte_).next(); } @Test public void testStringToByte() { - assertEquals((byte) 0, __.__("0").asNumber(N.nbyte).next()); - assertEquals((byte) 127, __.__("127").asNumber(N.nbyte).next()); - assertEquals((byte) -128, __.__("-128").asNumber(N.nbyte).next()); - assertEquals((byte) 42, __.__("42").asNumber(N.nbyte).next()); - assertEquals((byte) -42, __.__("-42").asNumber(N.nbyte).next()); - assertEquals((byte) 1, __.__("1").asNumber(N.nbyte).next()); + assertEquals((byte) 0, __.__("0").asNumber(N.byte_).next()); + assertEquals((byte) 127, __.__("127").asNumber(N.byte_).next()); + assertEquals((byte) -128, __.__("-128").asNumber(N.byte_).next()); + assertEquals((byte) 42, __.__("42").asNumber(N.byte_).next()); + assertEquals((byte) -42, __.__("-42").asNumber(N.byte_).next()); + assertEquals((byte) 1, __.__("1").asNumber(N.byte_).next()); } @Test public void testStringToShort() { - assertEquals((short) 0, __.__("0").asNumber(N.nshort).next()); - assertEquals((short) 32767, __.__("32767").asNumber(N.nshort).next()); - assertEquals((short) -32768, __.__("-32768").asNumber(N.nshort).next()); - assertEquals((short) 1000, __.__("1000").asNumber(N.nshort).next()); - assertEquals((short) -1000, __.__("-1000").asNumber(N.nshort).next()); - assertEquals((short) 255, __.__("255").asNumber(N.nshort).next()); + assertEquals((short) 0, __.__("0").asNumber(N.short_).next()); + assertEquals((short) 32767, __.__("32767").asNumber(N.short_).next()); + assertEquals((short) -32768, __.__("-32768").asNumber(N.short_).next()); + assertEquals((short) 1000, __.__("1000").asNumber(N.short_).next()); + assertEquals((short) -1000, __.__("-1000").asNumber(N.short_).next()); + assertEquals((short) 255, __.__("255").asNumber(N.short_).next()); } @Test public void testStringToInt() { - assertEquals(0, __.__("0").asNumber(N.nint).next()); - assertEquals(2147483647, __.__("2147483647").asNumber(N.nint).next()); - assertEquals(-2147483648, __.__("-2147483648").asNumber(N.nint).next()); - assertEquals(123456, __.__("123456").asNumber(N.nint).next()); - assertEquals(-123456, __.__("-123456").asNumber(N.nint).next()); - assertEquals(65536, __.__("65536").asNumber(N.nint).next()); + assertEquals(0, __.__("0").asNumber(N.int_).next()); + assertEquals(2147483647, __.__("2147483647").asNumber(N.int_).next()); + assertEquals(-2147483648, __.__("-2147483648").asNumber(N.int_).next()); + assertEquals(123456, __.__("123456").asNumber(N.int_).next()); + assertEquals(-123456, __.__("-123456").asNumber(N.int_).next()); + assertEquals(65536, __.__("65536").asNumber(N.int_).next()); } @Test public void testStringToLong() { - assertEquals(0L, __.__("0").asNumber(N.nlong).next()); - assertEquals(9223372036854775807L, __.__("9223372036854775807").asNumber(N.nlong).next()); - assertEquals(-9223372036854775808L, __.__("-9223372036854775808").asNumber(N.nlong).next()); - assertEquals(123456789L, __.__("123456789").asNumber(N.nlong).next()); - assertEquals(-123456789L, __.__("-123456789").asNumber(N.nlong).next()); - assertEquals(4294967296L, __.__("4294967296").asNumber(N.nlong).next()); + assertEquals(0L, __.__("0").asNumber(N.long_).next()); + assertEquals(9223372036854775807L, __.__("9223372036854775807").asNumber(N.long_).next()); + assertEquals(-9223372036854775808L, __.__("-9223372036854775808").asNumber(N.long_).next()); + assertEquals(123456789L, __.__("123456789").asNumber(N.long_).next()); + assertEquals(-123456789L, __.__("-123456789").asNumber(N.long_).next()); + assertEquals(4294967296L, __.__("4294967296").asNumber(N.long_).next()); } @Test public void testStringToFloat() { - assertEquals(0.0f, __.__("0.0").asNumber(N.nfloat).next()); - assertEquals(3.14f, __.__("3.14").asNumber(N.nfloat).next()); - assertEquals(-3.14f, __.__("-3.14").asNumber(N.nfloat).next()); - assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.nfloat).next()); - assertEquals(-1.23e-10f, __.__("-1.23e-10").asNumber(N.nfloat).next()); - assertEquals(Float.MAX_VALUE, __.__("3.4028235E38").asNumber(N.nfloat).next()); - assertEquals(Float.MIN_VALUE, __.__("1.4E-45").asNumber(N.nfloat).next()); + assertEquals(0.0f, __.__("0.0").asNumber(N.float_).next()); + assertEquals(3.14f, __.__("3.14").asNumber(N.float_).next()); + assertEquals(-3.14f, __.__("-3.14").asNumber(N.float_).next()); + assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.float_).next()); + assertEquals(-1.23e-10f, __.__("-1.23e-10").asNumber(N.float_).next()); + assertEquals(Float.MAX_VALUE, __.__("3.4028235E38").asNumber(N.float_).next()); + assertEquals(Float.MIN_VALUE, __.__("1.4E-45").asNumber(N.float_).next()); } @Test public void testStringToDouble() { - assertEquals(0.0, __.__("0.0").asNumber(N.ndouble).next()); - assertEquals(3.141592653589793, __.__("3.141592653589793").asNumber(N.ndouble).next()); - assertEquals(-3.141592653589793, __.__("-3.141592653589793").asNumber(N.ndouble).next()); - assertEquals(1.23e100, __.__("1.23e100").asNumber(N.ndouble).next()); - assertEquals(-1.23e-100, __.__("-1.23e-100").asNumber(N.ndouble).next()); - assertEquals(Double.MAX_VALUE, __.__("1.7976931348623157E308").asNumber(N.ndouble).next()); - assertEquals(Double.MIN_VALUE, __.__("4.9E-324").asNumber(N.ndouble).next()); + assertEquals(0.0, __.__("0.0").asNumber(N.double_).next()); + assertEquals(3.141592653589793, __.__("3.141592653589793").asNumber(N.double_).next()); + assertEquals(-3.141592653589793, __.__("-3.141592653589793").asNumber(N.double_).next()); + assertEquals(1.23e100, __.__("1.23e100").asNumber(N.double_).next()); + assertEquals(-1.23e-100, __.__("-1.23e-100").asNumber(N.double_).next()); + assertEquals(Double.MAX_VALUE, __.__("1.7976931348623157E308").asNumber(N.double_).next()); + assertEquals(Double.MIN_VALUE, __.__("4.9E-324").asNumber(N.double_).next()); } @Test public void testStringToBigInteger() { - assertEquals(new BigInteger("0"), __.__("0").asNumber(N.nbigInt).next()); + assertEquals(new BigInteger("0"), __.__("0").asNumber(N.bigInt).next()); assertEquals(new BigInteger("123456789012345678901234567890"), - __.__("123456789012345678901234567890").asNumber(N.nbigInt).next()); + __.__("123456789012345678901234567890").asNumber(N.bigInt).next()); assertEquals(new BigInteger("-123456789012345678901234567890"), - __.__("-123456789012345678901234567890").asNumber(N.nbigInt).next()); + __.__("-123456789012345678901234567890").asNumber(N.bigInt).next()); assertEquals(new BigInteger("999999999999999999999999999999999999999999999999999"), - __.__("999999999999999999999999999999999999999999999999999").asNumber(N.nbigInt).next()); - assertEquals(new BigInteger("1"), __.__("1").asNumber(N.nbigInt).next()); + __.__("999999999999999999999999999999999999999999999999999").asNumber(N.bigInt).next()); + assertEquals(new BigInteger("1"), __.__("1").asNumber(N.bigInt).next()); + assertEquals(new BigInteger("1000000000000000000000"), __.__(new Double("1000000000000000000000")).asNumber(N.bigInt).next()); } @Test public void testStringToBigDecimal() { - assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.nbigDecimal).next()); + assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.bigDecimal).next()); assertEquals(new BigDecimal("123456789012345678901234567890.123456789"), - __.__("123456789012345678901234567890.123456789").asNumber(N.nbigDecimal).next()); + __.__("123456789012345678901234567890.123456789").asNumber(N.bigDecimal).next()); assertEquals(new BigDecimal("-123456789012345678901234567890.123456789"), - __.__("-123456789012345678901234567890.123456789").asNumber(N.nbigDecimal).next()); + __.__("-123456789012345678901234567890.123456789").asNumber(N.bigDecimal).next()); // Note directly constructing BigDecimal returns 1E-39, but parsing then converting from double results in 1.0E-39 assertEquals(BigDecimal.valueOf(Double.parseDouble("0.000000000000000000000000000000000000001")), - __.__("0.000000000000000000000000000000000000001").asNumber(N.nbigDecimal).next()); - assertEquals(new BigDecimal("1.0"), __.__("1.0").asNumber(N.nbigDecimal).next()); + __.__("0.000000000000000000000000000000000000001").asNumber(N.bigDecimal).next()); + assertEquals(new BigDecimal("1.0"), __.__("1.0").asNumber(N.bigDecimal).next()); } // ===== EDGE CASE TESTS ===== @Test public void testCastInfinityAndNaN() { - assertEquals(Double.POSITIVE_INFINITY, __.__(Float.POSITIVE_INFINITY).asNumber(N.ndouble).next()); - assertEquals(Double.NEGATIVE_INFINITY, __.__(Float.NEGATIVE_INFINITY).asNumber(N.ndouble).next()); - assertEquals(Double.NaN, __.__(Float.NaN).asNumber(N.ndouble).next()); - assertEquals(Float.POSITIVE_INFINITY, __.__(Double.POSITIVE_INFINITY).asNumber(N.nfloat).next()); - assertEquals(Float.NEGATIVE_INFINITY, __.__(Double.NEGATIVE_INFINITY).asNumber(N.nfloat).next()); - assertEquals(Float.NaN, __.__(Double.NaN).asNumber(N.nfloat).next()); + assertEquals(Double.POSITIVE_INFINITY, __.__(Float.POSITIVE_INFINITY).asNumber(N.double_).next()); + assertEquals(Double.NEGATIVE_INFINITY, __.__(Float.NEGATIVE_INFINITY).asNumber(N.double_).next()); + assertEquals(Double.NaN, __.__(Float.NaN).asNumber(N.double_).next()); + assertEquals(Float.POSITIVE_INFINITY, __.__(Double.POSITIVE_INFINITY).asNumber(N.float_).next()); + assertEquals(Float.NEGATIVE_INFINITY, __.__(Double.NEGATIVE_INFINITY).asNumber(N.float_).next()); + assertEquals(Float.NaN, __.__(Double.NaN).asNumber(N.float_).next()); } @Test public void testStringWithWhitespace() { - assertEquals(42, __.__(" 42 ").asNumber(N.nint).next()); - assertEquals(42, __.__("\t42\n").asNumber(N.nint).next()); - assertEquals(3.14, __.__(" 3.14 ").asNumber(N.ndouble).next()); - assertEquals((byte) 127, __.__(" 127 ").asNumber(N.nbyte).next()); + assertEquals(42, __.__(" 42 ").asNumber(N.int_).next()); + assertEquals(42, __.__("\t42\n").asNumber(N.int_).next()); + assertEquals(3.14, __.__(" 3.14 ").asNumber(N.double_).next()); + assertEquals((byte) 127, __.__(" 127 ").asNumber(N.byte_).next()); } @Test public void testStringWithPlusSign() { - assertEquals(42, __.__("+42").asNumber(N.nint).next()); - assertEquals(3.14, __.__("+3.14").asNumber(N.ndouble).next()); - assertEquals((byte) 42, __.__("+42").asNumber(N.nbyte).next()); - assertEquals(new BigInteger("42"), __.__("+42").asNumber(N.nbigInt).next()); + assertEquals(42, __.__("+42").asNumber(N.int_).next()); + assertEquals(3.14, __.__("+3.14").asNumber(N.double_).next()); + assertEquals((byte) 42, __.__("+42").asNumber(N.byte_).next()); + assertEquals(new BigInteger("42"), __.__("+42").asNumber(N.bigInt).next()); } @Test public void testZeroValues() { - assertEquals((byte) 0, __.__("0").asNumber(N.nbyte).next()); - assertEquals((short) 0, __.__("0").asNumber(N.nshort).next()); - assertEquals(0, __.__("0").asNumber(N.nint).next()); - assertEquals(0L, __.__("0").asNumber(N.nlong).next()); - assertEquals(0.0f, __.__("0.0").asNumber(N.nfloat).next()); - assertEquals(0.0, __.__("0.0").asNumber(N.ndouble).next()); - assertEquals(new BigInteger("0"), __.__("0").asNumber(N.nbigInt).next()); - assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.nbigDecimal).next()); + assertEquals((byte) 0, __.__("0").asNumber(N.byte_).next()); + assertEquals((short) 0, __.__("0").asNumber(N.short_).next()); + assertEquals(0, __.__("0").asNumber(N.int_).next()); + assertEquals(0L, __.__("0").asNumber(N.long_).next()); + assertEquals(0.0f, __.__("0.0").asNumber(N.float_).next()); + assertEquals(0.0, __.__("0.0").asNumber(N.double_).next()); + assertEquals(new BigInteger("0"), __.__("0").asNumber(N.bigInt).next()); + assertEquals(new BigDecimal("0.0"), __.__("0.0").asNumber(N.bigDecimal).next()); } @Test public void testScientificNotation() { - assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.nfloat).next()); - assertEquals(1.23e10, __.__("1.23e10").asNumber(N.ndouble).next()); - assertEquals(1.23e-10f, __.__("1.23e-10").asNumber(N.nfloat).next()); - assertEquals(1.23e-10, __.__("1.23e-10").asNumber(N.ndouble).next()); - assertEquals(new BigDecimal("1.23E10"), __.__("1.23e10").asNumber(N.nbigDecimal).next()); + assertEquals(1.23e10f, __.__("1.23e10").asNumber(N.float_).next()); + assertEquals(1.23e10, __.__("1.23e10").asNumber(N.double_).next()); + assertEquals(1.23e-10f, __.__("1.23e-10").asNumber(N.float_).next()); + assertEquals(1.23e-10, __.__("1.23e-10").asNumber(N.double_).next()); + assertEquals(new BigDecimal("1.23E10"), __.__("1.23e10").asNumber(N.bigDecimal).next()); } // ===== OVERFLOW EXCEPTION TESTS ===== @Test(expected = ArithmeticException.class) public void shouldThrowCastExceptionConvertingInfinityToWholeNumber() { - __.__(Double.POSITIVE_INFINITY).asNumber(N.nlong).next(); + __.__(Double.POSITIVE_INFINITY).asNumber(N.long_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowCastExceptionConvertingNaNToWholeNumber() { - __.__(Double.NaN).asNumber(N.nint).next(); + __.__(Double.NaN).asNumber(N.int_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringByteOverflowsPositive() { - __.__("128").asNumber(N.nbyte).next(); + __.__("128").asNumber(N.byte_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringByteOverflowsNegative() { - __.__("-129").asNumber(N.nbyte).next(); + __.__("-129").asNumber(N.byte_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringShortOverflowsPositive() { - __.__("32768").asNumber(N.nshort).next(); + __.__("32768").asNumber(N.short_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringShortOverflowsNegative() { - __.__("-32769").asNumber(N.nshort).next(); + __.__("-32769").asNumber(N.short_).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenBigDecimalToShortOverflows() { + __.__(new BigDecimal(Long.MAX_VALUE+"1")).asNumber(N.short_).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenFloatToLongOverflows() { + __.__(new Float(Long.MAX_VALUE+"1")).asNumber(N.long_).next(); + } + + @Test(expected = ArithmeticException.class) + public void shouldThrowOverflowExceptionWhenDoubleToLongOverflows() { + __.__(new Double(Long.MAX_VALUE+"1")).asNumber(N.long_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringIntOverflowsPositive() { - __.__("2147483648").asNumber(N.nint).next(); + __.__("2147483648").asNumber(N.int_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringIntOverflowsNegative() { - __.__("-2147483649").asNumber(N.nint).next(); + __.__("-2147483649").asNumber(N.int_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringLongOverflowsPositive() { - __.__("9223372036854775809").asNumber(N.nlong).next(); + __.__("9223372036854775809").asNumber(N.long_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenStringLongOverflowsNegative() { - __.__("-9223372036854775809").asNumber(N.nlong).next(); + __.__("-9223372036854775809").asNumber(N.long_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenDoubleStringToFloatOverflows() { - __.__("3.5E38").asNumber(N.nfloat).next(); + __.__("3.5E38").asNumber(N.float_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenBigDecimalStringToDoubleOverflows() { - __.__("1.8E308").asNumber(N.ndouble).next(); + __.__("1.8E308").asNumber(N.double_).next(); } @Test(expected = ArithmeticException.class) public void shouldThrowOverflowExceptionWhenBigDecimalStringToFloatOverflows() { - __.__("1.8E308").asNumber(N.nfloat).next(); + __.__("1.8E308").asNumber(N.float_).next(); } // ===== INVALID FORMAT EXCEPTION TESTS ===== @@ -355,26 +371,26 @@ public void shouldThrowExceptionWhenInvalidDecimalFormat() { @Test public void testBoundaryValues() { - assertEquals(Byte.MAX_VALUE, __.__("127").asNumber(N.nbyte).next()); - assertEquals(Byte.MIN_VALUE, __.__("-128").asNumber(N.nbyte).next()); - assertEquals(Short.MAX_VALUE, __.__("32767").asNumber(N.nshort).next()); - assertEquals(Short.MIN_VALUE, __.__("-32768").asNumber(N.nshort).next()); - assertEquals(Integer.MAX_VALUE, __.__("2147483647").asNumber(N.nint).next()); - assertEquals(Integer.MIN_VALUE, __.__("-2147483648").asNumber(N.nint).next()); - assertEquals(Long.MAX_VALUE, __.__("9223372036854775807").asNumber(N.nlong).next()); - assertEquals(Long.MIN_VALUE, __.__("-9223372036854775808").asNumber(N.nlong).next()); + assertEquals(Byte.MAX_VALUE, __.__("127").asNumber(N.byte_).next()); + assertEquals(Byte.MIN_VALUE, __.__("-128").asNumber(N.byte_).next()); + assertEquals(Short.MAX_VALUE, __.__("32767").asNumber(N.short_).next()); + assertEquals(Short.MIN_VALUE, __.__("-32768").asNumber(N.short_).next()); + assertEquals(Integer.MAX_VALUE, __.__("2147483647").asNumber(N.int_).next()); + assertEquals(Integer.MIN_VALUE, __.__("-2147483648").asNumber(N.int_).next()); + assertEquals(Long.MAX_VALUE, __.__("9223372036854775807").asNumber(N.long_).next()); + assertEquals(Long.MIN_VALUE, __.__("-9223372036854775808").asNumber(N.long_).next()); } @Test public void testHighPrecisionDecimals() { assertEquals(new BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067"), - __.__("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067").asNumber(N.nbigDecimal).next()); + __.__("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067").asNumber(N.bigDecimal).next()); } @Test public void testVeryLargeNumbers() { String largeNumber = "12345678901234567890123456789012345678901234567890123456789012345678901234567890"; - assertEquals(new BigInteger(largeNumber), __.__(largeNumber).asNumber(N.nbigInt).next()); + assertEquals(new BigInteger(largeNumber), __.__(largeNumber).asNumber(N.bigInt).next()); } } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java index 20772da0c96..ac848f60253 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/util/NumberHelperTest.java @@ -715,73 +715,79 @@ public void shouldCoerceToConvertToFloatIfCanFit() { @Test public void shouldCastToReturnSameInstanceForSameClass() { final Integer value = 42; - assertEquals(value, NumberHelper.castTo(value, N.nint)); + assertEquals(value, NumberHelper.castTo(value, N.int_)); } @Test public void shouldCastToConvertToByte() { final Integer value = 42; - assertEquals(Byte.valueOf((byte) 42), NumberHelper.castTo(value, N.nbyte)); + assertEquals(Byte.valueOf((byte) 42), NumberHelper.castTo(value, N.byte_)); } @Test public void shouldCastToConvertToShort() { final Integer value = 42; - assertEquals(Short.valueOf((short) 42), NumberHelper.castTo(value, N.nshort)); + assertEquals(Short.valueOf((short) 42), NumberHelper.castTo(value, N.short_)); } @Test public void shouldCastToConvertToLong() { final Integer value = 42; - assertEquals(Long.valueOf(42L), NumberHelper.castTo(value, N.nlong)); + assertEquals(Long.valueOf(42L), NumberHelper.castTo(value, N.long_)); } @Test public void shouldCastToConvertToFloat() { final Integer value = 42; - assertEquals(Float.valueOf(42.0f), NumberHelper.castTo(value, N.nfloat)); + assertEquals(Float.valueOf(42.0f), NumberHelper.castTo(value, N.float_)); } @Test public void shouldCastToConvertToDouble() { final Integer value = 42; - assertEquals(Double.valueOf(42.0), NumberHelper.castTo(value, N.ndouble)); + assertEquals(Double.valueOf(42.0), NumberHelper.castTo(value, N.double_)); } @Test public void shouldCastToConvertToBigInteger() { final Integer value = 42; - assertEquals(BigInteger.valueOf(42), NumberHelper.castTo(value, N.nbigInt)); + assertEquals(BigInteger.valueOf(42), NumberHelper.castTo(value, N.bigInt)); + } + + @Test + public void shouldCastToConvertDoubleToBigInteger() { + final Double value = new Double("1000000000000000000000"); + assertEquals(new BigInteger("1000000000000000000000"), NumberHelper.castTo(value, N.bigInt)); } @Test public void shouldCastToConvertToBigDecimal() { final Integer value = 42; - assertEquals(BigDecimal.valueOf(42), NumberHelper.castTo(value, N.nbigDecimal)); + assertEquals(BigDecimal.valueOf(42), NumberHelper.castTo(value, N.bigDecimal)); } @Test(expected = ArithmeticException.class) public void shouldOverflowIfCannotFitInByte() { final Integer value = 128; - NumberHelper.castTo(value, N.nbyte); + NumberHelper.castTo(value, N.byte_); } @Test(expected = ArithmeticException.class) public void shouldOverflowIfCannotFitInShort() { final Integer value = 32768; - NumberHelper.castTo(value, N.nshort); + NumberHelper.castTo(value, N.short_); } @Test(expected = ArithmeticException.class) public void shouldOverflowIfCannotFitInInteger() { final Long value = 2147483648L; - NumberHelper.castTo(value, N.nint); + NumberHelper.castTo(value, N.int_); } @Test(expected = ArithmeticException.class) public void shouldOverflowIfCannotFitInFloat() { final Double value = Double.MAX_VALUE; - NumberHelper.castTo(value, N.nfloat); + NumberHelper.castTo(value, N.float_); } } diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs index 8996957b30c..b0081ba3d06 100644 --- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs +++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/N.cs @@ -35,25 +35,25 @@ private N(string enumValue) { } - public static N NByte => new N("nbyte"); - public static N NShort => new N("nshort"); - public static N NInt => new N("nint"); - public static N NLong => new N("nlong"); - public static N NFloat => new N("nfloat"); - public static N NDouble => new N("ndouble"); - public static N NBigInt => new N("nbigInt"); - public static N NBigDecimal => new N("nbigDecimal"); + public static N Byte => new N("byte"); + public static N Short => new N("short"); + public static N Int => new N("int"); + public static N Long => new N("long"); + public static N Float => new N("float"); + public static N Double => new N("double"); + public static N BigInt => new N("bigInt"); + public static N BigDecimal => new N("bigDecimal"); private static readonly Dictionary Properties = new() { - { "nbyte", NByte }, - { "nshort", NShort }, - { "nint", NInt }, - { "nlong", NLong }, - { "nfloat", NFloat }, - { "ndouble", NDouble }, - { "nbigInt", NBigInt }, - { "nbigDecimal", NBigDecimal }, + { "byte", Byte }, + { "short", Short }, + { "int", Int }, + { "long", Long }, + { "float", Float }, + { "double", Double }, + { "bigInt", BigInt }, + { "bigDecimal", BigDecimal }, }; /// diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 466499259a5..776bf8d6518 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -629,23 +629,23 @@ private static IDictionary, ITraversal>> {(g,p) =>g.Inject(new BigInteger(5)).AsNumber()}}, {"g_injectX5_0X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5.0).AsNumber()}}, {"g_injectX5_75fX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(5.75f).AsNumber()}}, - {"g_injectX5_43X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.43).AsNumber(N.Nint)}}, - {"g_injectX5_67X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(5.67).AsNumber(N.Nint)}}, - {"g_injectX5X_asNumberXN_nlongX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber(N.Nlong)}}, - {"g_injectX12X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(12).AsNumber(N.Nbyte)}}, - {"g_injectX32768X_asNumberXN_nshortX", new List, ITraversal>> {(g,p) =>g.Inject(32768).AsNumber(N.Nshort)}}, - {"g_injectX300X_asNumberXN_nbyteX", new List, ITraversal>> {(g,p) =>g.Inject(300).AsNumber(N.Nbyte)}}, + {"g_injectX5_43X_asNumberXN_intX", new List, ITraversal>> {(g,p) =>g.Inject(5.43).AsNumber(N.Int)}}, + {"g_injectX5_67X_asNumberXN_intX", new List, ITraversal>> {(g,p) =>g.Inject(5.67).AsNumber(N.Int)}}, + {"g_injectX5X_asNumberXN_longX", new List, ITraversal>> {(g,p) =>g.Inject(5).AsNumber(N.Long)}}, + {"g_injectX12X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject(12).AsNumber(N.Byte)}}, + {"g_injectX32768X_asNumberXN_shortX", new List, ITraversal>> {(g,p) =>g.Inject(32768).AsNumber(N.Short)}}, + {"g_injectX300X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject(300).AsNumber(N.Byte)}}, {"g_injectX5X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber()}}, - {"g_injectX5X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Nbyte)}}, - {"g_injectX1_000X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject("1,000").AsNumber(N.Nint)}}, + {"g_injectX5X_asNumberXN_byteX", new List, ITraversal>> {(g,p) =>g.Inject("5").AsNumber(N.Byte)}}, + {"g_injectX1_000X_asNumberXN_bigIntX", new List, ITraversal>> {(g,p) =>g.Inject("1,000").AsNumber(N.BigInt)}}, {"g_injectXtestX_asNumber", new List, ITraversal>> {(g,p) =>g.Inject("test").AsNumber()}}, {"g_injectX_1_2_3_4X_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).AsNumber()}}, {"g_injectX1_2_3_4X_unfold_asNumber", new List, ITraversal>> {(g,p) =>g.Inject(new List { 1, 2, 3, 4 }).Unfold().AsNumber()}}, {"g_injectX_1__2__3__4_X_asNumberXX_foldXX", new List, ITraversal>> {(g,p) =>g.Inject("1", 2, "3", 4).AsNumber().Fold()}}, - {"g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(N.Nbyte)}}, - {"g_VX1X_asNumberXN_nintX", new List, ITraversal>> {(g,p) =>g.Inject(null).AsNumber(N.Nint)}}, - {"g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX", new List, ITraversal>> {(g,p) =>g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(N.Nint)}}, - {"g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX", new List, ITraversal>> {(g,p) =>g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(N.Nlong)}}, + {"g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXbyteX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(N.Byte)}}, + {"g_VX1X_asNumberXN_intX", new List, ITraversal>> {(g,p) =>g.Inject(null).AsNumber(N.Int)}}, + {"g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXintX", new List, ITraversal>> {(g,p) =>g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(N.Int)}}, + {"g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXlongX", new List, ITraversal>> {(g,p) =>g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(N.Long)}}, {"g_injectX1_2X_asString", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString()}}, {"g_injectX1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"], p["xx2"]).AsString(Scope.Local)}}, {"g_injectXlist_1_2X_asStringXlocalX", new List, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).AsString(Scope.Local)}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 9beb85fe5f3..21ca5e90658 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -599,23 +599,23 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_injectX5nX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber()}}, "g_injectX5_0X_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.0).AsNumber()}}, "g_injectX5_75fX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.75).AsNumber()}}, - "g_injectX5_43X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.43).AsNumber(gremlingo.N.Nint)}}, - "g_injectX5_67X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.67).AsNumber(gremlingo.N.Nint)}}, - "g_injectX5X_asNumberXN_nlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber(gremlingo.N.Nlong)}}, - "g_injectX12X_asNumberXN_nbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(12).AsNumber(gremlingo.N.Nbyte)}}, - "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_injectX5_43X_asNumberXN_intX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.43).AsNumber(gremlingo.N.Int)}}, + "g_injectX5_67X_asNumberXN_intX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5.67).AsNumber(gremlingo.N.Int)}}, + "g_injectX5X_asNumberXN_longX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(5).AsNumber(gremlingo.N.Long)}}, + "g_injectX12X_asNumberXN_byteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(12).AsNumber(gremlingo.N.Byte)}}, + "g_injectX32768X_asNumberXN_shortX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(32768).AsNumber(gremlingo.N.Short)}}, + "g_injectX300X_asNumberXN_byteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(300).AsNumber(gremlingo.N.Byte)}}, "g_injectX5X_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("5").AsNumber()}}, - "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_injectX5X_asNumberXN_byteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("5").AsNumber(gremlingo.N.Byte)}}, + "g_injectX1_000X_asNumberXN_bigIntX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("1,000").AsNumber(gremlingo.N.BigInt)}}, "g_injectXtestX_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("test").AsNumber()}}, "g_injectX_1_2_3_4X_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).AsNumber()}}, "g_injectX1_2_3_4X_unfold_asNumber": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{1, 2, 3, 4}).Unfold().AsNumber()}}, "g_injectX_1__2__3__4_X_asNumberXX_foldXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject("1", 2, "3", 4).AsNumber().Fold()}}, - "g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(gremlingo.N.Nbyte)}}, - "g_VX1X_asNumberXN_nintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsNumber(gremlingo.N.Nint)}}, - "g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(gremlingo.N.Nint)}}, - "g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(gremlingo.N.Nlong)}}, + "g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXbyteX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"], p["xx3"], "4", "0x5").AsNumber().Sum().AsNumber(gremlingo.N.Byte)}}, + "g_VX1X_asNumberXN_intX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(nil).AsNumber(gremlingo.N.Int)}}, + "g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXintX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().As("a").Out("knows").As("b").Math("a + b").By("age").AsNumber(gremlingo.N.Int)}}, + "g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXlongX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithSideEffect("x", p["xx1"]).V().Values("age").Math("_ + x").AsNumber(gremlingo.N.Long)}}, "g_injectX1_2X_asString": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString()}}, "g_injectX1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"], p["xx2"]).AsString(gremlingo.Scope.Local)}}, "g_injectXlist_1_2X_asStringXlocalX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).AsString(gremlingo.Scope.Local)}}, diff --git a/gremlin-go/driver/serializer.go b/gremlin-go/driver/serializer.go index 8a2dec9d56e..314d2767f93 100644 --- a/gremlin-go/driver/serializer.go +++ b/gremlin-go/driver/serializer.go @@ -328,7 +328,7 @@ func initDeserializers() { tType: enumReader, directionType: enumReader, dtType: enumReader, - nType: enumReader, + nType: enumReader, bindingType: bindingReader, // Metrics diff --git a/gremlin-go/driver/traversal.go b/gremlin-go/driver/traversal.go index bd21fb6a063..811ff971e1e 100644 --- a/gremlin-go/driver/traversal.go +++ b/gremlin-go/driver/traversal.go @@ -364,33 +364,33 @@ type n string type ns struct { // number type byte - Nbyte n + Byte n // number type short - Nshort n + Short n // number type int - Nint n + Int n // number type long - Nlong n + Long n // number type float - Nfloat n + Float n // number type double - Ndouble n + Double n // number type bigInt - NbigInt n + BigInt n // number type bigDecimal - NbigDecimal n + BigDecimal n } // N is a set of operations for denoting number types during conversion. var N = ns{ - Nbyte: "nbyte", - Nshort: "nshort", - Nint: "nint", - Nlong: "nlong", - Nfloat: "nfloat", - Ndouble: "ndouble", - NbigInt: "nbigInt", - NbigDecimal: "nbigDecimal", + Byte: "byte", + Short: "short", + Int: "int", + Long: "long", + Float: "float", + Double: "double", + BigInt: "bigInt", + BigDecimal: "bigDecimal", } type operator string diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js index 84c8482f8fd..2bc9705fe9d 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/traversal.js @@ -487,6 +487,14 @@ function toDirectionEnum(typeName, keys) { return result; } +function toNEnum(typeName, keys) { + const result = {}; + keys.split(' ').forEach((k) => { + result[k] = new EnumValue(typeName, k.replace('_', '')); + }); + return result; +} + class EnumValue { constructor(typeName, elementName) { this.typeName = typeName; @@ -512,7 +520,7 @@ module.exports = { column: toEnum('Column', 'keys values'), direction: toDirectionEnum('Direction', 'BOTH IN OUT from_ to'), dt: toEnum('DT', 'second minute hour day'), - n: toEnum('N', 'nbyte nshort nint nlong nfloat ndouble nbigint nbigdecimal'), + n: toNEnum('N', 'byte_ short_ int_ long_ float_ double_ bigInt bigDecimal'), graphSONVersion: toEnum('GraphSONVersion', 'V1_0 V2_0 V3_0'), gryoVersion: toEnum('GryoVersion', 'V1_0 V3_0'), merge: toEnum('Merge', 'onCreate onMatch outV inV'), 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 9ad1b33b8ea..aab5f6a21ed 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 @@ -630,23 +630,23 @@ const gremlins = { g_injectX5nX_asNumber: [function({g}) { return g.inject(5).asNumber() }], g_injectX5_0X_asNumber: [function({g}) { return g.inject(5.0).asNumber() }], g_injectX5_75fX_asNumber: [function({g}) { return g.inject(5.75).asNumber() }], - g_injectX5_43X_asNumberXN_nintX: [function({g}) { return g.inject(5.43).asNumber(N.nint) }], - g_injectX5_67X_asNumberXN_nintX: [function({g}) { return g.inject(5.67).asNumber(N.nint) }], - g_injectX5X_asNumberXN_nlongX: [function({g}) { return g.inject(5).asNumber(N.nlong) }], - g_injectX12X_asNumberXN_nbyteX: [function({g}) { return g.inject(12).asNumber(N.nbyte) }], - 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_injectX5_43X_asNumberXN_intX: [function({g}) { return g.inject(5.43).asNumber(N.int_) }], + g_injectX5_67X_asNumberXN_intX: [function({g}) { return g.inject(5.67).asNumber(N.int_) }], + g_injectX5X_asNumberXN_longX: [function({g}) { return g.inject(5).asNumber(N.long_) }], + g_injectX12X_asNumberXN_byteX: [function({g}) { return g.inject(12).asNumber(N.byte_) }], + g_injectX32768X_asNumberXN_shortX: [function({g}) { return g.inject(32768).asNumber(N.short_) }], + g_injectX300X_asNumberXN_byteX: [function({g}) { return g.inject(300).asNumber(N.byte_) }], g_injectX5X_asNumber: [function({g}) { return g.inject("5").asNumber() }], - 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_injectX5X_asNumberXN_byteX: [function({g}) { return g.inject("5").asNumber(N.byte_) }], + g_injectX1_000X_asNumberXN_bigIntX: [function({g}) { return g.inject("1,000").asNumber(N.bigInt) }], g_injectXtestX_asNumber: [function({g}) { return g.inject("test").asNumber() }], g_injectX_1_2_3_4X_asNumber: [function({g}) { return g.inject([1, 2, 3, 4]).asNumber() }], g_injectX1_2_3_4X_unfold_asNumber: [function({g}) { return g.inject([1, 2, 3, 4]).unfold().asNumber() }], g_injectX_1__2__3__4_X_asNumberXX_foldXX: [function({g}) { return g.inject("1", 2, "3", 4).asNumber().fold() }], - g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX: [function({g, xx1, xx3, xx2}) { return g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.nbyte) }], - g_VX1X_asNumberXN_nintX: [function({g}) { return g.inject(null).asNumber(N.nint) }], - g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX: [function({g}) { return g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.nint) }], - g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX: [function({g, xx1}) { return g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.nlong) }], + g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXbyteX: [function({g, xx1, xx3, xx2}) { return g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.byte_) }], + g_VX1X_asNumberXN_intX: [function({g}) { return g.inject(null).asNumber(N.int_) }], + g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXintX: [function({g}) { return g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.int_) }], + g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXlongX: [function({g, xx1}) { return g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.long_) }], g_injectX1_2X_asString: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString() }], g_injectX1_2X_asStringXlocalX: [function({g, xx1, xx2}) { return g.inject(xx1, xx2).asString(Scope.local) }], g_injectXlist_1_2X_asStringXlocalX: [function({g, xx1}) { return g.inject(xx1).asString(Scope.local) }], diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js index b9399a6ffff..d723c85cd92 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/AnySerializer-test.js @@ -176,8 +176,8 @@ describe('GraphBinary.AnySerializer', () => { { v: new t.EnumValue('Merge', 'onMatch'), b: [ DataType.MERGE,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x07, ...from('onMatch') ] }, - { v: new t.EnumValue('N', 'nbyte'), - b: [ DataType.N,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x05, ...from('nbyte') ] + { v: new t.EnumValue('N', 'byte'), + b: [ DataType.N,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x04, ...from('byte') ] }, { v: new t.EnumValue('Operator', 'addAll'), b: [ DataType.OPERATOR,0x00, DataType.STRING,0x00, 0x00,0x00,0x00,0x06, ...from('addAll') ] @@ -522,7 +522,7 @@ describe('GraphBinary.AnySerializer', () => { // N { v:null, b:[0x30,0x01] }, - { v:new t.EnumValue('N','nbyte'), b:[0x30,0x00, 0x03,0x00, 0x00,0x00,0x00,0x05, ...from('nbyte')] }, + { v:new t.EnumValue('N','byte'), b:[0x30,0x00, 0x03,0x00, 0x00,0x00,0x00,0x04, ...from('byte')] }, // OPERATOR { v:null, b:[0x19,0x01] }, diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/EnumSerializer-test.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/EnumSerializer-test.js index a5765995da1..4dc0abd616f 100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/EnumSerializer-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphbinary/EnumSerializer-test.js @@ -36,6 +36,7 @@ describe('GraphBinary.EnumSerializer', () => { { name: 'Cardinality', code: from([0x16]), enum: t.cardinality }, { name: 'Column', code: from([0x17]), enum: t.column }, { name: 'Direction', code: from([0x18]), enum: t.direction }, + { name: 'N', code: from([0x30]), enum: t.n }, { name: 'Operator', code: from([0x19]), enum: t.operator }, { name: 'Order', code: from([0x1A]), enum: t.order }, { name: 'Pick', code: from([0x1B]), enum: t.pick }, diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4 index d7037a4fffd..00031355f67 100644 --- a/gremlin-language/src/main/antlr4/Gremlin.g4 +++ b/gremlin-language/src/main/antlr4/Gremlin.g4 @@ -1963,15 +1963,15 @@ K_BARRIER: 'barrier'; K_BARRIERU: 'Barrier'; K_BEGIN: 'begin'; K_BETWEEN: 'between'; -K_BIGDECIMAL: 'nbigDecimal'; -K_BIGINT: 'nbigInt'; +K_BIGDECIMAL: 'bigDecimal'; +K_BIGINT: 'bigInt'; K_BOTH: 'both'; K_BOTHU: 'BOTH'; K_BOTHE: 'bothE'; K_BOTHV: 'bothV'; K_BRANCH: 'branch'; K_BY: 'by'; -K_BYTE: 'nbyte'; +K_BYTE: 'byte'; K_CALL: 'call'; K_CAP: 'cap'; K_CARDINALITY: 'Cardinality'; @@ -2003,7 +2003,7 @@ K_DIRECTION: 'Direction'; K_DISJUNCT: 'disjunct'; K_DISTANCE: 'distance'; K_DIV: 'div'; -K_DOUBLE: 'ndouble'; +K_DOUBLE: 'double'; K_DROP: 'drop'; K_DT: 'DT'; K_E: 'E'; @@ -2019,7 +2019,7 @@ K_FALSE: 'false'; K_FILTER: 'filter'; K_FIRST: 'first'; K_FLATMAP: 'flatMap'; -K_FLOAT: 'nfloat'; +K_FLOAT: 'float'; K_FOLD: 'fold'; K_FORMAT: 'format'; K_FROM: 'from'; @@ -2052,7 +2052,7 @@ K_INDEX: 'index'; K_INFINITY: 'Infinity'; K_INJECT: 'inject'; K_INSIDE: 'inside'; -K_INT: 'nint'; +K_INT: 'int'; K_INTERSECT: 'intersect'; K_INV: 'inV'; K_IOU: 'IO'; @@ -2068,7 +2068,7 @@ K_LENGTH: 'length'; K_LIMIT: 'limit'; K_LIST: 'list'; K_LOCAL: 'local'; -K_LONG: 'nlong'; +K_LONG: 'long'; K_LOOPS: 'loops'; K_LT: 'lt'; K_LTE: 'lte'; @@ -2149,7 +2149,7 @@ K_SET: 'set'; K_SHORTESTPATHU: 'ShortestPath'; K_SHORTESTPATH: 'shortestPath'; K_SHUFFLE: 'shuffle'; -K_SHORT: 'nshort'; +K_SHORT: 'short'; K_SIDEEFFECT: 'sideEffect'; K_SIMPLEPATH: 'simplePath'; K_SINGLE: 'single'; diff --git a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java index d68af03fc7c..d25f59d98ea 100644 --- a/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java +++ b/gremlin-language/src/main/java/org/apache/tinkerpop/gremlin/language/corpus/DocumentationReader.java @@ -122,6 +122,8 @@ private static String replaceVariables(final String gremlin) { replace(".getClass()", ""). replace("result.toArray()", "4"). replace("vA.value('amount')", "0.0"). - replace("vA", "\"vA\""); + replace("vA", "\"vA\""). + replace("N.int_", "N.int"). + replace("N.byte_", "N.byte"); } } diff --git a/gremlin-python/src/main/python/gremlin_python/process/traversal.py b/gremlin-python/src/main/python/gremlin_python/process/traversal.py index 4f93bf97add..70b4902da8e 100644 --- a/gremlin-python/src/main/python/gremlin_python/process/traversal.py +++ b/gremlin-python/src/main/python/gremlin_python/process/traversal.py @@ -208,15 +208,16 @@ def process(f): statics.add_static('in_v', Merge.in_v) statics.add_static('out_v', Merge.out_v) -N = Enum('N', ' nbyte nshort nint nlong nfloat ndouble nbigInt nbigDecimal') -statics.add_static('nbyte', N.nbyte) -statics.add_static('nshort', N.nshort) -statics.add_static('nint', N.nint) -statics.add_static('nlong', N.nlong) -statics.add_static('nfloat', N.nfloat) -statics.add_static('ndouble', N.ndouble) -statics.add_static('nbigInt', N.nbigInt) -statics.add_static('nbigDecimal', N.nbigDecimal) +N = Enum('N', ' byte short int long float double big_int big_decimal') +# no keyword collision so enum is defined as lang, but has collision with type aliases defined in statics +statics.add_static('byte_', N.byte) +statics.add_static('short_', N.short) +statics.add_static('int_', N.int) +statics.add_static('long_', N.long) +statics.add_static('float_', N.float) +statics.add_static('double_', N.double) +statics.add_static('big_int', N.big_int) +statics.add_static('big_decimal', N.big_decimal) Order = Enum('Order', ' asc desc shuffle') @@ -326,7 +327,7 @@ def within(*args): return P("within", list(args[0])) else: return P("within", list(args)) - + @staticmethod def without(*args): if len(args) == 1 and type(args[0]) == list: diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index f33c343d580..38a2fdaa4f3 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -602,23 +602,23 @@ 'g_injectX5nX_asNumber': [(lambda g:g.inject(bigint(5)).as_number())], 'g_injectX5_0X_asNumber': [(lambda g:g.inject(5.0).as_number())], 'g_injectX5_75fX_asNumber': [(lambda g:g.inject(5.75).as_number())], - 'g_injectX5_43X_asNumberXN_nintX': [(lambda g:g.inject(5.43).as_number(N.nint))], - 'g_injectX5_67X_asNumberXN_nintX': [(lambda g:g.inject(5.67).as_number(N.nint))], - 'g_injectX5X_asNumberXN_nlongX': [(lambda g:g.inject(5).as_number(N.nlong))], - 'g_injectX12X_asNumberXN_nbyteX': [(lambda g:g.inject(12).as_number(N.nbyte))], - '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_injectX5_43X_asNumberXN_intX': [(lambda g:g.inject(5.43).as_number(N.int))], + 'g_injectX5_67X_asNumberXN_intX': [(lambda g:g.inject(5.67).as_number(N.int))], + 'g_injectX5X_asNumberXN_longX': [(lambda g:g.inject(5).as_number(N.long))], + 'g_injectX12X_asNumberXN_byteX': [(lambda g:g.inject(12).as_number(N.byte))], + 'g_injectX32768X_asNumberXN_shortX': [(lambda g:g.inject(32768).as_number(N.short))], + 'g_injectX300X_asNumberXN_byteX': [(lambda g:g.inject(300).as_number(N.byte))], 'g_injectX5X_asNumber': [(lambda g:g.inject('5').as_number())], - '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_injectX5X_asNumberXN_byteX': [(lambda g:g.inject('5').as_number(N.byte))], + 'g_injectX1_000X_asNumberXN_bigIntX': [(lambda g:g.inject('1,000').as_number(N.big_int))], 'g_injectXtestX_asNumber': [(lambda g:g.inject('test').as_number())], 'g_injectX_1_2_3_4X_asNumber': [(lambda g:g.inject([1, 2, 3, 4]).as_number())], 'g_injectX1_2_3_4X_unfold_asNumber': [(lambda g:g.inject([1, 2, 3, 4]).unfold().as_number())], 'g_injectX_1__2__3__4_X_asNumberXX_foldXX': [(lambda g:g.inject('1', 2, '3', 4).as_number().fold())], - 'g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX': [(lambda g, xx1=None,xx3=None,xx2=None:g.inject(xx1, xx2, xx3, '4', '0x5').as_number().sum_().as_number(N.nbyte))], - 'g_VX1X_asNumberXN_nintX': [(lambda g:g.inject(None).as_number(N.nint))], - 'g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX': [(lambda g:g.V().as_('a').out('knows').as_('b').math('a + b').by('age').as_number(N.nint))], - 'g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX': [(lambda g, xx1=None:g.with_side_effect('x', xx1).V().values('age').math('_ + x').as_number(N.nlong))], + 'g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXbyteX': [(lambda g, xx1=None,xx3=None,xx2=None:g.inject(xx1, xx2, xx3, '4', '0x5').as_number().sum_().as_number(N.byte))], + 'g_VX1X_asNumberXN_intX': [(lambda g:g.inject(None).as_number(N.int))], + 'g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXintX': [(lambda g:g.V().as_('a').out('knows').as_('b').math('a + b').by('age').as_number(N.int))], + 'g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXlongX': [(lambda g, xx1=None:g.with_side_effect('x', xx1).V().values('age').math('_ + x').as_number(N.long))], 'g_injectX1_2X_asString': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string())], 'g_injectX1_2X_asStringXlocalX': [(lambda g, xx1=None,xx2=None:g.inject(xx1, xx2).as_string(Scope.local))], 'g_injectXlist_1_2X_asStringXlocalX': [(lambda g, xx1=None:g.inject(xx1).as_string(Scope.local))], diff --git a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py index fe402a10917..eb6beee5c63 100644 --- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py +++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py @@ -25,7 +25,7 @@ from gremlin_python.process.traversal import Traverser from gremlin_python.process.traversal import TraversalStrategy from gremlin_python.process.traversal import Bindings -from gremlin_python.process.traversal import P, Order, T, N +from gremlin_python.process.traversal import P, Order, T from gremlin_python.process.graph_traversal import __ from gremlin_python.process.anonymous_traversal import traversal from gremlin_python.structure.graph import Vertex 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 cb36ba87b67..1777cfb785c 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 @@ -103,11 +103,11 @@ Feature: Step - asNumber() | d[5.75].f | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5_43X_asNumberXN_nintX + Scenario: g_injectX5_43X_asNumberXN_intX Given the empty graph And the traversal of """ - g.inject(5.43).asNumber(N.nint) + g.inject(5.43).asNumber(N.int) """ When iterated to list Then the result should be unordered @@ -115,11 +115,11 @@ Feature: Step - asNumber() | d[5].i | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5_67X_asNumberXN_nintX + Scenario: g_injectX5_67X_asNumberXN_intX Given the empty graph And the traversal of """ - g.inject(5.67).asNumber(N.nint) + g.inject(5.67).asNumber(N.int) """ When iterated to list Then the result should be unordered @@ -127,11 +127,11 @@ Feature: Step - asNumber() | d[5].i | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX5X_asNumberXN_nlongX + Scenario: g_injectX5X_asNumberXN_longX Given the empty graph And the traversal of """ - g.inject(5).asNumber(N.nlong) + g.inject(5).asNumber(N.long) """ When iterated to list Then the result should be unordered @@ -139,11 +139,11 @@ Feature: Step - asNumber() | d[5].l | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX12X_asNumberXN_nbyteX + Scenario: g_injectX12X_asNumberXN_byteX Given the empty graph And the traversal of """ - g.inject(12).asNumber(N.nbyte) + g.inject(12).asNumber(N.byte) """ When iterated to list Then the result should be unordered @@ -151,21 +151,21 @@ Feature: Step - asNumber() | d[12].b | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX32768X_asNumberXN_nshortX + Scenario: g_injectX32768X_asNumberXN_shortX Given the empty graph And the traversal of """ - g.inject(32768).asNumber(N.nshort) + g.inject(32768).asNumber(N.short) """ When iterated to list Then the traversal will raise an error with message containing text of "Can't convert number of type Integer to Short due to overflow." @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX300X_asNumberXN_nbyteX + Scenario: g_injectX300X_asNumberXN_byteX Given the empty graph And the traversal of """ - g.inject(300).asNumber(N.nbyte) + g.inject(300).asNumber(N.byte) """ When iterated to list Then the traversal will raise an error with message containing text of "Can't convert number of type Integer to Byte due to overflow." @@ -187,7 +187,7 @@ Feature: Step - asNumber() Given the empty graph And the traversal of """ - g.inject("5").asNumber(N.nbyte) + g.inject("5").asNumber(N.byte) """ When iterated to list Then the result should be unordered @@ -195,11 +195,11 @@ Feature: Step - asNumber() | d[5].b | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX1_000X_asNumberXN_nintX + Scenario: g_injectX1_000X_asNumberXN_bigIntX Given the empty graph And the traversal of """ - g.inject("1,000").asNumber(N.nint) + g.inject("1,000").asNumber(N.bigInt) """ When iterated to list Then the traversal will raise an error with message containing text of "Can't parse string '1,000' as number." @@ -252,14 +252,14 @@ Feature: Step - asNumber() | l[d[1].i,d[2].i,d[3].i,d[4].i] | @GraphComputerVerificationInjectionNotSupported - Scenario: g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXnbyteX + Scenario: g_injectX1_2_3_4_0x5X_asNumber_sum_asNumberXbyteX Given the empty graph And using the parameter xx1 defined as "d[1.0].d" And using the parameter xx2 defined as "d[2].i" And using the parameter xx3 defined as "d[3].l" And the traversal of """ - g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.nbyte) + g.inject(xx1, xx2, xx3, "4", "0x5").asNumber().sum().asNumber(N.byte) """ When iterated to list Then the result should be unordered @@ -267,21 +267,21 @@ Feature: Step - asNumber() | d[15].b | @GraphComputerVerificationInjectionNotSupported - Scenario: g_VX1X_asNumberXN_nintX + Scenario: g_VX1X_asNumberXN_intX Given the empty graph And the traversal of """ - g.inject(null).asNumber(N.nint) + g.inject(null).asNumber(N.int) """ When iterated to list Then the traversal will raise an error with message containing text of "Can't parse type null as number." @GraphComputerVerificationReferenceOnly - Scenario: g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXnintX + Scenario: g_V_asXaX_outXknowsX_asXbX_mathXa_plus_bX_byXageX_asNumberXintX Given the modern graph And the traversal of """ - g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.nint) + g.V().as("a").out("knows").as("b").math("a + b").by("age").asNumber(N.int) """ When iterated to list Then the result should be unordered @@ -289,12 +289,12 @@ Feature: Step - asNumber() | d[56].i | | d[61].i | - Scenario: g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXnlongX + Scenario: g_withSideEffectXx_100X_V_age_mathX__plus_xX_asNumberXlongX Given the modern graph And using the parameter xx1 defined as "d[100].i" And the traversal of """ - g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.nlong) + g.withSideEffect("x", xx1).V().values("age").math("_ + x").asNumber(N.long) """ When iterated to list Then the result should be unordered