This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch gvalue in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 3775b6fe71298d6131ed19e9b1cf96d978e486c5 Author: Stephen Mallette <[email protected]> AuthorDate: Wed Jul 31 16:15:58 2024 -0400 wip --- .../language/grammar/TraversalMethodVisitor.java | 61 +++++++++++++---- .../grammar/TraversalSourceSpawnMethodVisitor.java | 14 +++- .../traversal/dsl/graph/GraphTraversal.java | 72 +++++++++++++++++++ .../traversal/dsl/graph/GraphTraversalSource.java | 46 +++++++++++++ .../gremlin/process/traversal/step/GType.java | 16 ++++- .../gremlin/process/traversal/step/GValue.java | 5 +- .../process/traversal/step/filter/CoinStep.java | 6 +- .../process/traversal/step/map/CombineStep.java | 11 +-- .../process/traversal/step/map/ConjoinStep.java | 17 +++-- .../process/traversal/step/map/ConstantStep.java | 6 +- .../process/traversal/step/map/GraphStep.java | 80 +++++++++++++--------- .../process/traversal/step/map/IntersectStep.java | 11 +-- .../process/traversal/step/map/MergeStep.java | 5 +- .../traversal/step/map/TraversalMergeStep.java | 19 +++-- .../traversal/step/sideEffect/InjectStep.java | 14 ++-- .../process/traversal/step/util/AbstractStep.java | 27 ++++++++ .../process/traversal/util/ListFunction.java | 16 ++--- .../gremlin/process/traversal/step/GValueTest.java | 30 ++++---- .../tinkerpop/gremlin/features/StepDefinition.java | 32 +++++++-- .../apache/tinkerpop/gremlin/features/World.java | 9 +++ .../gremlin/test/features/map/Conjoin.feature | 12 ++++ .../traversal/step/sideEffect/Neo4jGraphStep.java | 4 +- .../traversal/step/sideEffect/TinkerGraphStep.java | 4 +- .../tinkerpop/gremlin/tinkergraph/TinkerWorld.java | 5 ++ 24 files changed, 399 insertions(+), 123 deletions(-) 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 4f523c092f..fe500e6210 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 @@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.GType; import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality; +import java.util.Collection; import java.util.Map; import java.util.function.BiFunction; @@ -40,11 +41,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> /** * This object is used to append the traversal methods. */ - private final GraphTraversal graphTraversal; + private final GraphTraversal.Admin graphTraversal; public TraversalMethodVisitor(final GremlinAntlrToJava antlr, final GraphTraversal graphTraversal) { super(antlr, graphTraversal); - this.graphTraversal = graphTraversal; + this.graphTraversal = graphTraversal.asAdmin(); } /** @@ -92,7 +93,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_mergeV_Map(final GremlinParser.TraversalMethod_mergeV_MapContext ctx) { - return this.graphTraversal.mergeV(antlr.argumentVisitor.parseMap(ctx.genericLiteralMapNullableArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.MAP) + return graphTraversal.mergeV((GValue<Map<Object, Object>>) literalOrVar); + else + return graphTraversal.mergeV((Map) literalOrVar); } /** @@ -124,7 +129,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_mergeE_Map(final GremlinParser.TraversalMethod_mergeE_MapContext ctx) { - return this.graphTraversal.mergeE(antlr.argumentVisitor.parseMap(ctx.genericLiteralMapNullableArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.MAP) + return graphTraversal.mergeE((GValue<Map<Object, Object>>) literalOrVar); + else + return graphTraversal.mergeE((Map) literalOrVar); } /** @@ -435,7 +444,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_combine_Object(final GremlinParser.TraversalMethod_combine_ObjectContext ctx) { - return graphTraversal.combine(antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType().isCollection()) + return graphTraversal.combine((GValue<Object>) literalOrVar); + else + return graphTraversal.combine(literalOrVar); } /** @@ -447,9 +460,9 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> if (literalOrVar instanceof Number) return graphTraversal.coin(((Number) literalOrVar).doubleValue()); else if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType().isNumeric()) - return graphTraversal.asAdmin().coin((GValue<Double>) literalOrVar); + return graphTraversal.coin((GValue<Double>) literalOrVar); else - throw new IllegalArgumentException("coin() argument must be a double"); + throw new IllegalArgumentException("coin argument must be a double"); } @@ -458,7 +471,13 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_conjoin_String(final GremlinParser.TraversalMethod_conjoin_StringContext ctx) { - return graphTraversal.conjoin(antlr.argumentVisitor.parseString(ctx.stringArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitStringArgument(ctx.stringArgument()); + if (literalOrVar instanceof String) + return graphTraversal.conjoin((String) literalOrVar); + else if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.STRING) + return graphTraversal.conjoin((GValue<String>) literalOrVar); + else + throw new IllegalArgumentException("conjoin argument must be a string"); } /** @@ -868,7 +887,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_intersect_Object(final GremlinParser.TraversalMethod_intersect_ObjectContext ctx) { - return graphTraversal.intersect(antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType().isCollection()) + return graphTraversal.intersect((GValue<Object>) literalOrVar); + else + return graphTraversal.intersect(literalOrVar); } /** @@ -1068,7 +1091,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_merge_Object(final GremlinParser.TraversalMethod_merge_ObjectContext ctx) { - return graphTraversal.merge(antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType().isCollection()) + return graphTraversal.merge((GValue<Object>) literalOrVar); + else + return graphTraversal.merge(literalOrVar); } /** @@ -1108,8 +1135,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_option_Object_Traversal(final GremlinParser.TraversalMethod_option_Object_TraversalContext ctx) { - return graphTraversal.option(antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument()), - antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralArgument(ctx.genericLiteralArgument()); + if (literalOrVar instanceof GValue) + return graphTraversal.option((GValue) literalOrVar, antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal())); + else + return graphTraversal.option(literalOrVar, antlr.tvisitor.visitNestedTraversal(ctx.nestedTraversal())); } /** @@ -1125,8 +1155,11 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal> */ @Override public GraphTraversal visitTraversalMethod_option_Merge_Map(final GremlinParser.TraversalMethod_option_Merge_MapContext ctx) { - return graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()), - (Map) antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.MAP) + return graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()), (GValue<Map>) literalOrVar); + else + return graphTraversal.option(antlr.argumentVisitor.parseMerge(ctx.traversalMergeArgument()), (Map) literalOrVar); } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java index 2227635f80..8e5ba3ded2 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalSourceSpawnMethodVisitor.java @@ -21,6 +21,8 @@ package org.apache.tinkerpop.gremlin.language.grammar; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; +import org.apache.tinkerpop.gremlin.process.traversal.step.GType; import java.util.Map; @@ -119,7 +121,11 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor */ @Override public GraphTraversal visitTraversalSourceSpawnMethod_mergeV_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeV_MapContext ctx) { - return this.traversalSource.mergeV(antlr.argumentVisitor.parseMap(ctx.genericLiteralMapNullableArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.MAP) + return this.traversalSource.asAdmin().mergeV((GValue) literalOrVar); + else + return this.traversalSource.mergeV((Map) literalOrVar); } /** @@ -143,7 +149,11 @@ public class TraversalSourceSpawnMethodVisitor extends DefaultGremlinBaseVisitor */ @Override public GraphTraversal visitTraversalSourceSpawnMethod_mergeE_Map(final GremlinParser.TraversalSourceSpawnMethod_mergeE_MapContext ctx) { - return this.traversalSource.mergeE(antlr.argumentVisitor.parseMap(ctx.genericLiteralMapNullableArgument())); + final Object literalOrVar = antlr.argumentVisitor.visitGenericLiteralMapNullableArgument(ctx.genericLiteralMapNullableArgument()); + if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType() == GType.MAP) + return this.traversalSource.asAdmin().mergeE((GValue) literalOrVar); + else + return this.traversalSource.mergeE((Map) literalOrVar); } /** 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 30f6a16b9f..7967688a87 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 @@ -459,6 +459,30 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { return TraversalHelper.addHasContainer(this.asAdmin(), new HasContainer(T.label.getAccessor(), labels.length == 1 ? P.eq(labels[0]) : P.within(labels))); } + /** + * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the + * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see + * the documentation of such steps to understand the usage context. + * + * @param token the token that would trigger this option which may be a {@link Pick}, {@link Merge}, + * a {@link Traversal}, {@link Predicate}, or object depending on the step being modulated. + * @param traversalOption the option as a traversal + * @return the traversal with the modulated step + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#choose-step" target="_blank">Reference Documentation - Choose Step</a> + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergev-step" target="_blank">Reference Documentation - MergeV Step</a> + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#mergee-step" target="_blank">Reference Documentation - MergeE Step</a> + * @since 3.0.0-incubating + */ + public default <M, E2> GraphTraversal<S, E> option(final GValue<M> token, final Traversal<?, E2> traversalOption) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.option, token, traversalOption); + + // handle null similar to how option() with Map handles it, otherwise we get a NPE if this one gets used + final Traversal.Admin<E,E2> t = null == traversalOption ? + new ConstantTraversal<>(null) : (Traversal.Admin<E, E2>) traversalOption.asAdmin(); + ((TraversalOptionParent<M, E, E2>) this.asAdmin().getEndStep()).addChildOption(token.get(), t); + return this; + } + /** * This is a step modulator to a {@link TraversalOptionParent} like {@code choose()} or {@code mergeV()} where the * provided argument associated to the {@code token} is applied according to the semantics of the step. Please see @@ -502,6 +526,54 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { return this; } + /** + * Combines the list traverser and list argument. Also known as concatenation or append. + * + * @return the traversal with an appended {@link CombineStep}. + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#combine-step" target="_blank">Reference Documentation - Combine Step</a> + * @since 3.7.3 + */ + public default GraphTraversal<S, List<?>> combine(final GValue<Object> values) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.combine, values); + return this.asAdmin().addStep(new CombineStep<>(this.asAdmin(), values)); + } + + /** + * Joins together the elements of the incoming list traverser together with the provided delimiter. + * + * @return the traversal with an appended {@link ConjoinStep}. + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#conjoin-step" target="_blank">Reference Documentation - Conjoin Step</a> + * @since 3.7.3 + */ + public default GraphTraversal<S, String> conjoin(final GValue<String> delimiter) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.conjoin, delimiter); + return this.asAdmin().addStep(new ConjoinStep<>(this.asAdmin(), delimiter)); + } + + /** + * Calculates the intersection between the list traverser and list argument. + * + * @return the traversal with an appended {@link IntersectStep}. + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#intersect-step" target="_blank">Reference Documentation - Intersect Step</a> + * @since 3.7.3 + */ + public default GraphTraversal<S, Set<?>> intersect(final GValue<Object> values) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.intersect, values); + return this.asAdmin().addStep(new IntersectStep<>(this.asAdmin(), values)); + } + + /** + * Merges the list traverser and list argument. Also known as union. + * + * @return the traversal with an appended {@link TraversalMergeStep}. + * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#merge-step" target="_blank">Reference Documentation - Merge Step</a> + * @since 3.7.1 + */ + public default <E2> GraphTraversal<S, E2> merge(final GValue<Object> values) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.merge, values); + return this.asAdmin().addStep(new TraversalMergeStep<>(this.asAdmin(), values)); + } + @Override public default <E2> GraphTraversal.Admin<S, E2> addStep(final Step<?, E2> step) { return (GraphTraversal.Admin<S, E2>) Traversal.Admin.super.addStep((Step) step); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java index dc15306fce..0c0670a7ca 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java @@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.branch.UnionStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddEdgeStartStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.AddVertexStartStep; @@ -68,6 +69,7 @@ public class GraphTraversalSource implements TraversalSource { protected final Graph graph; protected TraversalStrategies strategies; protected Bytecode bytecode = new Bytecode(); + protected Admin admin; //////////////// @@ -100,6 +102,12 @@ public class GraphTraversalSource implements TraversalSource { this.strategies.addStrategies(new RemoteStrategy(connection)); } + public GraphTraversalSource.Admin asAdmin() { + if (null == this.admin) + this.admin = new Admin(); + return this.admin; + } + @Override public Optional<Class<?>> getAnonymousTraversalClass() { return Optional.of(__.class); @@ -624,4 +632,42 @@ public class GraphTraversalSource implements TraversalSource { return StringFactory.traversalSourceString(this); } + + /** + * This class masks spawn steps that are more reserved for advanced usage. + */ + public class Admin { + + /** + * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Vertex} using a + * {@code Map} as an argument. The {@code Map} represents search criteria and will match each of the supplied + * key/value pairs where the keys may be {@code String} property values or a value of {@link T}. If a match is not + * made it will use that search criteria to create the new {@link Vertex}. + * + * @param searchCreate This {@code Map} can have a key of {@link T} or a {@code String}. + * @since 3.6.0 + */ + public GraphTraversal<Vertex, Vertex> mergeV(final GValue<Map<Object, Object>> searchCreate) { + final GraphTraversalSource clone = GraphTraversalSource.this.clone(); + clone.bytecode.addStep(GraphTraversal.Symbols.mergeV, searchCreate); + final GraphTraversal.Admin<Vertex, Vertex> traversal = new DefaultGraphTraversal<>(clone); + return traversal.addStep(new MergeVertexStep(traversal, true, searchCreate)); + } + + /** + * Spawns a {@link GraphTraversal} by doing a merge (i.e. upsert) style operation for an {@link Edge} using a + * {@code Map} as an argument. + * + * @param searchCreate This {@code Map} can have a key of {@link T} {@link Direction} or a {@code String}. + * @since 4.0.0 + */ + public GraphTraversal<Edge, Edge> mergeE(final GValue<Map<?, Object>> searchCreate) { + final GraphTraversalSource clone = GraphTraversalSource.this.clone(); + clone.bytecode.addStep(GraphTraversal.Symbols.mergeE, searchCreate); + final GraphTraversal.Admin<Edge, Edge> traversal = new DefaultGraphTraversal<>(clone); + return traversal.addStep(new MergeEdgeStep(traversal, true, searchCreate)); + } + + } + } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GType.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GType.java index 89a8b79098..58c1fdfe4d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GType.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GType.java @@ -52,12 +52,26 @@ public enum GType { GType() {} /** - * Returns true if the type is a number. + * Returns {@code true} if the type is a number. */ public boolean isNumeric() { return this == INTEGER || this == DOUBLE || this == LONG || this == BIG_INTEGER || this == BIG_DECIMAL; } + /** + * Returns {@code true} if the type is a collection.v + */ + public boolean isCollection() { + return this == LIST || this == SET; + } + + /** + * Returns {@code true} if the type is an element. + */ + public boolean isElement() { + return this == VERTEX || this == EDGE; + } + /** * Convert an object to a matching {@link GType} and if not matched return {@link GType#UNKNOWN}. */ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java index 4de2ca1f8e..e05a9e91f4 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java @@ -66,8 +66,8 @@ public class GValue<V> implements Cloneable, Serializable { /** * Gets the name of the variable if it was defined as such and returns empty if the value was a literal. */ - public Optional<String> getName() { - return Optional.ofNullable(this.name); + public String getName() { + return this.name; } /** @@ -123,6 +123,7 @@ public class GValue<V> implements Cloneable, Serializable { public static <V> GValue<V> of(final String name, final V value) { return new GValue<>(name, GType.getType(value), value); } + /** * Create a new {@code GValue} for a string value. */ diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java index e220952200..f9cfda6399 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/CoinStep.java @@ -58,14 +58,10 @@ public final class CoinStep<S> extends FilterStep<S> implements Seedable { random.setSeed(seed); } - public GValue<Double> getProbabilityGValue() { + public GValue<Double> getProbability() { return probability; } - public double getProbability() { - return this.probability.get(); - } - @Override protected boolean filter(final Traverser.Admin<S> traverser) { long newBulk = 0l; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CombineStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CombineStep.java index ccf601acab..587b7a7c72 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CombineStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/CombineStep.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.ListFunction; @@ -37,7 +38,7 @@ import java.util.Set; */ public final class CombineStep<S, E> extends ScalarMapStep<S, List<?>> implements TraversalParent, ListFunction { private Traversal.Admin<S, E> valueTraversal; - private Object parameterItems; + private GValue<Object> parameterItems; public CombineStep(final Traversal.Admin traversal, final Object values) { super(traversal); @@ -45,7 +46,7 @@ public final class CombineStep<S, E> extends ScalarMapStep<S, List<?>> implement if (values instanceof Traversal) { valueTraversal = integrateChild(((Traversal<S, E>) values).asAdmin()); } else { - parameterItems = values; + parameterItems = values instanceof GValue ? (GValue<Object>) values : GValue.of(values); } } @@ -53,9 +54,9 @@ public final class CombineStep<S, E> extends ScalarMapStep<S, List<?>> implement public String getStepName() { return "combine"; } @Override - protected List<?> map(Traverser.Admin<S> traverser) { + protected List<?> map(final Traverser.Admin<S> traverser) { final Collection listA = convertTraverserToCollection(traverser); - final Collection listB = (null != valueTraversal) ? convertTraversalToCollection(traverser, valueTraversal) : convertArgumentToCollection(parameterItems); + final Collection listB = (null != valueTraversal) ? convertTraversalToCollection(traverser, valueTraversal) : convertArgumentToCollection(parameterItems.get()); final List combined = new ArrayList(listA); combined.addAll(listB); @@ -68,7 +69,7 @@ public final class CombineStep<S, E> extends ScalarMapStep<S, List<?>> implement return (null == valueTraversal) ? Collections.emptyList() : Collections.singletonList(valueTraversal); } - public Object getParameterItems() { + public GValue<Object> getParameterItems() { return parameterItems; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java index ce6818b473..94457df007 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.ListFunction; @@ -34,11 +35,15 @@ import java.util.Set; * A map step that returns the result of joining every element in the traverser using the delimiter argument. */ public final class ConjoinStep<S> extends ScalarMapStep<S, String> implements ListFunction { - private String delimiter; + private GValue<String> delimiter; public ConjoinStep(final Traversal.Admin traversal, final String delimiter) { + this(traversal, GValue.of(delimiter)); + } + + public ConjoinStep(final Traversal.Admin traversal, final GValue<String> delimiter) { super(traversal); - if (null == delimiter) { throw new IllegalArgumentException("Input delimiter to conjoin step can't be null."); } + if (null == delimiter || null == delimiter.get()) { throw new IllegalArgumentException("Input delimiter to conjoin step can't be null."); } this.delimiter = delimiter; } @@ -46,7 +51,7 @@ public final class ConjoinStep<S> extends ScalarMapStep<S, String> implements Li public String getStepName() { return "conjoin"; } @Override - protected String map(Traverser.Admin<S> traverser) { + protected String map(final Traverser.Admin<S> traverser) { final Collection elements = convertTraverserToCollection(traverser); if (elements.isEmpty()) { return ""; } @@ -54,19 +59,19 @@ public final class ConjoinStep<S> extends ScalarMapStep<S, String> implements Li for (Object elem : elements) { if (elem != null) { - joinResult.append(String.valueOf(elem)).append(delimiter); + joinResult.append(elem).append(delimiter.get()); } } if (joinResult.length() != 0) { - joinResult.delete(joinResult.length() - delimiter.length(), joinResult.length()); + joinResult.delete(joinResult.length() - delimiter.get().length(), joinResult.length()); return joinResult.toString(); } else { return null; } } - public String getDelimiter() { + public GValue<String> getDelimiter() { return this.delimiter; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java index bf554d1aa9..245a443e5a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConstantStep.java @@ -41,11 +41,7 @@ public class ConstantStep<S, E> extends ScalarMapStep<S, E> { this.constant = null == constant ? GValue.of(null) : constant; } - public E getConstant() { - return this.constant.get(); - } - - public GValue<E> getConstantGValue() { + public GValue<E> getConstant() { return this.constant; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java index ee151aed12..5c03500d6e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/GraphStep.java @@ -44,6 +44,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; /** @@ -54,7 +55,7 @@ public class GraphStep<S, E extends Element> extends AbstractStep<S, E> implemen protected Parameters parameters = new Parameters(); protected final Class<E> returnClass; - protected Object[] ids; + protected GValue<?>[] ids; protected transient Supplier<Iterator<E>> iteratorSupplier; protected boolean isStart; protected boolean done = false; @@ -65,30 +66,42 @@ public class GraphStep<S, E extends Element> extends AbstractStep<S, E> implemen public GraphStep(final Traversal.Admin traversal, final Class<E> returnClass, final boolean isStart, final Object... ids) { super(traversal); this.returnClass = returnClass; - this.ids = (ids != null && ids.length == 1 && ids[0] instanceof Collection) ? ((Collection) ids[0]).toArray(new Object[((Collection) ids[0]).size()]) : ids; + + // if ids is a single collection like g.V(['a','b','c']), then unroll it into an array of ids + this.ids = convertToGValues(tryUnrollSingleCollectionArgument(ids)); + this.isStart = isStart; this.iteratorSupplier = () -> (Iterator<E>) (Vertex.class.isAssignableFrom(this.returnClass) ? - this.getTraversal().getGraph().get().vertices(convertGValuesToIds()) : - this.getTraversal().getGraph().get().edges(convertGValuesToIds())); + this.getTraversal().getGraph().get().vertices(resolveToValues(this.ids)) : + this.getTraversal().getGraph().get().edges(resolveToValues(this.ids))); } /** - * Converts {@link GValue} objects the ids array to their values to prevent them from leaking to the Graph API. + * Unrolls a single collection argument into an array of ids. This is useful for steps like + * {@code g.V(['a','b','c'])}. */ - private Object[] convertGValuesToIds() { - final Object[] newIds = new Object[this.ids.length]; - for (int i = 0; i < this.ids.length; i++) { - if (newIds[i] instanceof GValue) { - newIds[i] = ((GValue) this.ids[i]).get(); - } else { - newIds[i] = this.ids[i]; - } + protected static Object[] tryUnrollSingleCollectionArgument(final Object[] ids) { + final Object[] tempIds; + if (ids != null && ids.length == 1) { + final Optional<Collection> opt; + if (ids[0] instanceof GValue && ((GValue<?>) ids[0]).getType().isCollection()) + opt = Optional.of((Collection) ((GValue) ids[0]).get()); + else if (ids[0] instanceof Collection) + opt = Optional.of((Collection) ids[0]); + else + opt = Optional.empty(); + + if (opt.isPresent()) { + tempIds = opt.get().toArray(new Object[opt.get().size()]); + } else + tempIds = ids; + } else { + tempIds = ids; } - return newIds; + return tempIds; } - public String toString() { return StringFactory.stepString(this, this.returnClass.getSimpleName().toLowerCase(), Arrays.toString(this.ids)); } @@ -127,24 +140,28 @@ public class GraphStep<S, E extends Element> extends AbstractStep<S, E> implemen this.iteratorSupplier = iteratorSupplier; } - public Object[] getIds() { + /** + * Get the ids associated with this step. If there are {@link GValue} objects present they will be returned + * alongside literal ids. Prefer {@link #getResolvedIds()} if you prefer to work with literal ids only. + */ + public GValue[] getIds() { return this.ids; } + /** + * Gets the ids associated with this step as literal values rather than {@link GValue} objects. + */ + public Object[] getResolvedIds() { + return resolveToValues(this.ids); + } + public void addIds(final Object... newIds) { - if ((this.ids == null || this.ids.length == 0) && - newIds.length == 1 && - newIds[0] instanceof Collection && ((Collection) newIds[0]).isEmpty()) - this.ids = null; - else - this.ids = ArrayUtils.addAll(this.ids, - (newIds.length == 1 && newIds[0] instanceof Collection) ? - ((Collection) newIds[0]).toArray(new Object[((Collection) newIds[0]).size()]) : - newIds); + final GValue[] gvalues = convertToGValues(tryUnrollSingleCollectionArgument(newIds)); + this.ids = ArrayUtils.addAll(this.ids, gvalues); } public void clearIds() { - this.ids = new Object[0]; + this.ids = new GValue[0]; } @Override @@ -157,13 +174,10 @@ public class GraphStep<S, E extends Element> extends AbstractStep<S, E> implemen if (null != this.ids) { // if this is going to OLAP, convert to ids so you don't serialize elements for (int i = 0; i < this.ids.length; i++) { - - // spare the Graph API from GValue objects, as they are Gremlin level objects - if (this.ids[i] instanceof GValue) - this.ids[i] = ((GValue) this.ids[i]).get(); - - if (this.ids[i] instanceof Element) - this.ids[i] = ((Element) this.ids[i]).id(); + final GValue<?> current = this.ids[i]; + if (current.getType().isElement()) { + this.ids[i] = GValue.of(current.getName(), ((Element) current.get()).id()); + } } } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java index 651dec7502..93e5885e18 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/IntersectStep.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.ListFunction; @@ -36,7 +37,7 @@ import java.util.Set; */ public final class IntersectStep<S, E> extends ScalarMapStep<S, Set<?>> implements TraversalParent, ListFunction { private Traversal.Admin<S, E> valueTraversal; - private Object parameterItems; + private GValue<Object> parameterItems; public IntersectStep(final Traversal.Admin traversal, final Object values) { super(traversal); @@ -44,7 +45,7 @@ public final class IntersectStep<S, E> extends ScalarMapStep<S, Set<?>> implemen if (values instanceof Traversal) { valueTraversal = integrateChild(((Traversal<S, E>) values).asAdmin()); } else { - parameterItems = values; + parameterItems = values instanceof GValue ? (GValue<Object>) values : GValue.of(values); } } @@ -52,7 +53,7 @@ public final class IntersectStep<S, E> extends ScalarMapStep<S, Set<?>> implemen return this.valueTraversal; } - public Object getParameterItems() { + public GValue<Object> getParameterItems() { return this.parameterItems; } @@ -60,9 +61,9 @@ public final class IntersectStep<S, E> extends ScalarMapStep<S, Set<?>> implemen public String getStepName() { return "intersect"; } @Override - protected Set<?> map(Traverser.Admin<S> traverser) { + protected Set<?> map(final Traverser.Admin<S> traverser) { final Set setA = convertTraverserToSet(traverser); - final Collection setB = (null != valueTraversal) ? convertTraversalToCollection(traverser, this.valueTraversal) : convertArgumentToCollection(parameterItems); + final Collection setB = (null != valueTraversal) ? convertTraversalToCollection(traverser, this.valueTraversal) : convertArgumentToCollection(parameterItems.get()); final Set intersection = new HashSet(); for (Object element : setB) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java index e4649724af..5020b27410 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeStep.java @@ -332,8 +332,9 @@ public abstract class MergeStep<S, E, C> extends FlatMapStep<S, E> /** * null Map == empty Map */ - protected Map materializeMap(final Traverser.Admin<S> traverser, Traversal.Admin<S, ?> mapTraversal) { - Map map = (Map) TraversalUtil.apply(traverser, mapTraversal); + protected Map materializeMap(final Traverser.Admin<S> traverser, final Traversal.Admin<S, ?> mapTraversal) { + final Object o = TraversalUtil.apply(traverser, mapTraversal); + Map map = o instanceof GValue ? (Map) ((GValue) o).get() : (Map) o; // PartitionStrategy uses parameters as a mechanism for setting the partition key. trying to be as specific // as possible here wrt parameters usage to avoid misuse diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMergeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMergeStep.java index 3e339482b7..74d5c185de 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMergeStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/TraversalMergeStep.java @@ -20,6 +20,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.map; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.process.traversal.util.ListFunction; @@ -40,7 +41,7 @@ import java.util.Set; */ public final class TraversalMergeStep<S, E> extends ScalarMapStep<S, E> implements TraversalParent, ListFunction { private Traversal.Admin<S, E> valueTraversal; - private Object parameterItems; + private GValue<Object> parameterItems; public TraversalMergeStep(final Traversal.Admin traversal, final Object values) { super(traversal); @@ -48,10 +49,18 @@ public final class TraversalMergeStep<S, E> extends ScalarMapStep<S, E> implemen if (values instanceof Traversal) { valueTraversal = integrateChild(((Traversal<S, E>) values).asAdmin()); } else { - parameterItems = values; + parameterItems = values instanceof GValue ? (GValue<Object>) values : GValue.of(values); } } + public Traversal.Admin<S, E> getValueTraversal() { + return valueTraversal; + } + + public GValue<Object> getParameterItems() { + return parameterItems; + } + @Override public String getStepName() { return "merge"; } @@ -61,7 +70,7 @@ public final class TraversalMergeStep<S, E> extends ScalarMapStep<S, E> implemen final Map mapA = (incoming instanceof Map) ? (Map) incoming : null; if (mapA != null) { - final Object mapB = (valueTraversal != null) ? TraversalUtil.apply(traverser, valueTraversal) : parameterItems; + final Object mapB = (valueTraversal != null) ? TraversalUtil.apply(traverser, valueTraversal) : parameterItems.get(); if (!(mapB instanceof Map)) { throw new IllegalArgumentException( String.format( @@ -76,13 +85,13 @@ public final class TraversalMergeStep<S, E> extends ScalarMapStep<S, E> implemen } else { final Collection listA = convertTraverserToCollection(traverser); - if (parameterItems instanceof Map) { + if (parameterItems.get() instanceof Map) { throw new IllegalArgumentException(getStepName() + " step type mismatch: expected argument to be Iterable but got Map"); } final Collection listB = (null != valueTraversal) ? convertTraversalToCollection(traverser, valueTraversal) - : convertArgumentToCollection(parameterItems); + : convertArgumentToCollection(parameterItems.get()); final Set elements = new HashSet(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java index 0502f18541..ab57c05ce5 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/InjectStep.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.util.iterator.ArrayIterator; /** @@ -26,13 +27,13 @@ import org.apache.tinkerpop.gremlin.util.iterator.ArrayIterator; */ public final class InjectStep<S> extends StartStep<S> { - private final S[] injections; + private final GValue<S>[] injections; @SafeVarargs public InjectStep(final Traversal.Admin traversal, final S... injections) { super(traversal); - this.injections = injections; - this.start = new ArrayIterator<>(this.injections); + this.injections = convertToGValues(injections); + this.start = new ArrayIterator<>(resolveToValues(this.injections)); } @Override @@ -45,10 +46,13 @@ public final class InjectStep<S> extends StartStep<S> { @Override public void reset() { super.reset(); - this.start = new ArrayIterator<>(this.injections); + this.start = new ArrayIterator<>(resolveToValues(this.injections)); } - public S[] getInjections() { + /** + * Get the injections of the step. + */ + public GValue<S>[] getInjections() { return this.injections; } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java index d4545ead5c..15ddacdc91 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/AbstractStep.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.step.util; import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.EmptyTraverser; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet; import org.apache.tinkerpop.gremlin.process.traversal.util.EmptyTraversal; @@ -33,6 +34,7 @@ import java.util.LinkedHashSet; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.stream.Stream; /** * @author Marko A. Rodriguez (http://markorodriguez.com) @@ -229,4 +231,29 @@ public abstract class AbstractStep<S, E> implements Step<S, E> { return traverser; } + /** + * The elements in object array argument are examined to see if they are {@link GValue} objects. If they are, they + * are preserved as is. If they are not then they are wrapped in a {@link GValue} object. + */ + protected static <T> GValue<T>[] convertToGValues(final Object[] args) { + return Stream.of(args).map(id -> { + if (id instanceof GValue) + return (GValue<?>) id; + else + return GValue.of(id); + }).toArray(GValue[]::new); + } + + /** + * Converts {@link GValue} objects the argument array to their values to prevent them from leaking to the Graph API. + * Providers extending from this step should use this method to get actual values to prevent any {@link GValue} + * objects from leaking to the Graph API. + */ + protected Object[] resolveToValues(final GValue<?>[] gvalues) { + final Object[] newIds = new Object[gvalues.length]; + for (int i = 0; i < gvalues.length; i++) { + newIds[i] = gvalues[i].get(); + } + return newIds; + } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ListFunction.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ListFunction.java index ab693b9e29..5bf91c41e8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ListFunction.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ListFunction.java @@ -39,7 +39,7 @@ public interface ListFunction { * @param iterable an Iterable or array. * @return The iterable type as a Collection or null if argument isn't iterable. */ - public static Collection asCollection(Object iterable) { + public static Collection asCollection(final Object iterable) { if (iterable instanceof Collection) { return (Collection) iterable; } else if ((iterable != null && iterable.getClass().isArray()) || iterable instanceof Iterable) { @@ -56,7 +56,7 @@ public interface ListFunction { * @param iterable an Iterable or array. * @return The iterable type as a Collection or null if argument isn't iterable. */ - public static Set asSet(Object iterable) { + public static Set asSet(final Object iterable) { if (iterable instanceof Iterable || iterable != null && iterable.getClass().isArray()) { return IteratorUtils.asSet(iterable); } else { @@ -71,7 +71,7 @@ public interface ListFunction { */ public String getStepName(); - public default Collection convertArgumentToCollection(Object arg) { + public default Collection convertArgumentToCollection(final Object arg) { if (null == arg) { throw new IllegalArgumentException( String.format("Argument provided for %s step can't be null.", getStepName())); @@ -90,7 +90,7 @@ public interface ListFunction { return incoming; } - public default <S> Collection convertTraverserToCollection(Traverser.Admin<S> traverser) { + public default <S> Collection convertTraverserToCollection(final Traverser.Admin<S> traverser) { final S items = traverser.get(); if (null == items) { @@ -111,7 +111,7 @@ public interface ListFunction { return incoming; } - public default <S, E> Collection convertTraversalToCollection(Traverser.Admin<S> traverser, final Traversal.Admin<S, E> traversal) { + public default <S, E> Collection convertTraversalToCollection(final Traverser.Admin<S> traverser, final Traversal.Admin<S, E> traversal) { final Object array = TraversalUtil.apply(traverser, traversal); if (null == array) { @@ -134,7 +134,7 @@ public interface ListFunction { return input; } - public default Set convertArgumentToSet(Object arg) { + public default Set convertArgumentToSet(final Object arg) { if (null == arg) { throw new IllegalArgumentException( String.format("Argument provided for %s step can't be null.", getStepName())); @@ -153,7 +153,7 @@ public interface ListFunction { return incoming; } - public default <S> Set convertTraverserToSet(Traverser.Admin<S> traverser) { + public default <S> Set convertTraverserToSet(final Traverser.Admin<S> traverser) { final S items = traverser.get(); if (null == items) { @@ -174,7 +174,7 @@ public interface ListFunction { return incoming; } - public default <S, E> Set convertTraversalToSet(Traverser.Admin<S> traverser, final Traversal.Admin<S, E> traversal) { + public default <S, E> Set convertTraversalToSet(final Traverser.Admin<S> traverser, final Traversal.Admin<S, E> traversal) { final Object array = TraversalUtil.apply(traverser, traversal); if (null == array) { diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java index fcfac9b9d9..d1742e3e29 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java @@ -54,7 +54,7 @@ public class GValueTest { final GValue<Integer> gValue = GValue.of("varName", 123); assertEquals(123, gValue.get().intValue()); assertEquals(GType.INTEGER, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -71,7 +71,7 @@ public class GValueTest { final GValue<String> gValue = GValue.ofString("varName", "test"); assertEquals("test", gValue.get()); assertEquals(GType.STRING, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -88,7 +88,7 @@ public class GValueTest { final GValue<Integer> gValue = GValue.ofInteger("varName", 123); assertEquals(123, gValue.get().intValue()); assertEquals(GType.INTEGER, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -105,7 +105,7 @@ public class GValueTest { final GValue<Boolean> gValue = GValue.ofBoolean("varName", true); assertEquals(true, gValue.get()); assertEquals(GType.BOOLEAN, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -122,7 +122,7 @@ public class GValueTest { final GValue<Double> gValue = GValue.ofDouble("varName", 123.45); assertEquals(123.45, gValue.get(), 0.0); assertEquals(GType.DOUBLE, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -139,7 +139,7 @@ public class GValueTest { final GValue<BigInteger> gValue = GValue.ofBigInteger("varName", BigInteger.ONE); assertEquals(BigInteger.ONE, gValue.get()); assertEquals(GType.BIG_INTEGER, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -156,7 +156,7 @@ public class GValueTest { final GValue<BigDecimal> gValue = GValue.ofBigDecimal("varName", BigDecimal.ONE); assertEquals(BigDecimal.ONE, gValue.get()); assertEquals(GType.BIG_DECIMAL, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -173,7 +173,7 @@ public class GValueTest { final GValue<Long> gValue = GValue.ofLong("varName", 123L); assertEquals(123L, gValue.get().longValue()); assertEquals(GType.LONG, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -196,7 +196,7 @@ public class GValueTest { final GValue<Map> gValue = GValue.ofMap("varName", map); assertEquals(map, gValue.get()); assertEquals(GType.MAP, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -215,7 +215,7 @@ public class GValueTest { final GValue<List> gValue = GValue.ofList("varName", list); assertEquals(list, gValue.get()); assertEquals(GType.LIST, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -234,7 +234,7 @@ public class GValueTest { final GValue<Set> gValue = GValue.ofSet("varName", set); assertEquals(set, gValue.get()); assertEquals(GType.SET, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -253,7 +253,7 @@ public class GValueTest { final GValue<Vertex> gValue = GValue.ofVertex("varName", vertex); assertEquals(vertex, gValue.get()); assertEquals(GType.VERTEX, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -272,7 +272,7 @@ public class GValueTest { final GValue<Edge> gValue = GValue.ofEdge("varName", edge); assertEquals(edge, gValue.get()); assertEquals(GType.EDGE, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -291,7 +291,7 @@ public class GValueTest { final GValue<Path> gValue = GValue.ofPath("varName", path); assertEquals(path, gValue.get()); assertEquals(GType.PATH, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } @@ -310,7 +310,7 @@ public class GValueTest { final GValue<Property> gValue = GValue.ofProperty("varName", property); assertEquals(property, gValue.get()); assertEquals(GType.PROPERTY, gValue.getType()); - assertEquals("varName", gValue.getName().get()); + assertEquals("varName", gValue.getName()); assertThat(gValue.isVariable(), is(true)); } } \ No newline at end of file diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java index 27f36888cf..380313e29a 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java @@ -32,6 +32,7 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.apache.tinkerpop.gremlin.language.grammar.GremlinAntlrToJava; import org.apache.tinkerpop.gremlin.language.grammar.GremlinLexer; import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; +import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver; import org.apache.tinkerpop.gremlin.language.translator.GremlinTranslator; import org.apache.tinkerpop.gremlin.language.translator.Translator; import org.apache.tinkerpop.gremlin.process.traversal.Merge; @@ -92,7 +93,7 @@ public final class StepDefinition { private World world; private GraphTraversalSource g; - private final Map<String, String> stringParameters = new HashMap<>(); + private final Map<String, Object> stringParameters = new HashMap<>(); private Traversal traversal; private Object result; private Throwable error; @@ -285,14 +286,26 @@ public final class StepDefinition { @Given("using the parameter {word} defined as {string}") public void usingTheParameterXDefinedAsX(final String key, final String value) { - stringParameters.put(key, convertToString(value)); + // when parameters are used literally, they are converted to string representations that can be recognized by + // the grammar and parsed inline. when they are used as variables, they are converted to objects that can be + // applied to the script as variables. + if (world.useParametersLiterally()) + stringParameters.put(key, convertToString(value)); + else + stringParameters.put(key, convertToObject(value)); } @Given("the traversal of") public void theTraversalOf(final String docString) { try { - final String gremlin = tryUpdateDataFilePath(docString); - traversal = parseGremlin(applyParameters(gremlin)); + final String rawGremlin = tryUpdateDataFilePath(docString); + + // when parameters are used literally, they are converted to string representations that can be recognized by + // the grammar and parsed inline. when they are used as variables, they are converted to objects that can be + // applied to the script as variables. + final String gremlin = world.useParametersLiterally() ? applyParameters(rawGremlin) : rawGremlin; + + traversal = parseGremlin(gremlin); } catch (Exception ex) { ex.printStackTrace(); error = ex; @@ -447,7 +460,14 @@ public final class StepDefinition { final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(normalizedGremlin)); final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer)); final GremlinParser.QueryContext ctx = parser.query(); - return (Traversal) new GremlinAntlrToJava(g).visitQuery(ctx); + + // when parameters are used literally, they are converted to string representations that can be recognized by + // the grammar and parsed inline. when they are used as variables, they are converted to objects that can be + // applied to the script as variables. + if (world.useParametersLiterally()) + return (Traversal) new GremlinAntlrToJava(g).visitQuery(ctx); + else + return (Traversal) new GremlinAntlrToJava(g, new VariableResolver.DefaultVariableResolver(this.stringParameters)).visitQuery(ctx); } private List<Object> translateResultsToActual() { @@ -571,7 +591,7 @@ public final class StepDefinition { final List<String> paramNames = new ArrayList<>(stringParameters.keySet()); paramNames.sort((a,b) -> b.length() - a.length()); for (String k : paramNames) { - replaced = replaced.replace(k, stringParameters.get(k)); + replaced = replaced.replace(k, stringParameters.get(k).toString()); } return replaced; } diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java index 30e6daadb2..2dc31751da 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/World.java @@ -81,4 +81,13 @@ public interface World { public default String convertIdToScript(final Object id, final Class<? extends Element> type) { return id.toString(); } + + /** + * Determines if the test should use parameters literally or treat them as variables to be applied to the script. + * By default, they are treated literally. + */ + public default boolean useParametersLiterally() { + return true; + } + } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Conjoin.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Conjoin.feature index 9d9d54547e..4c9cdb483b 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Conjoin.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Conjoin.feature @@ -70,6 +70,18 @@ Feature: Step - conjoin() | result | | 27;29;32;35 | + Scenario: g_V_valuesXageX_order_fold_conjoinX_X + Given the modern graph + And using the parameter xx1 defined as "/" + And the traversal of + """ + g.V().values("age").order().fold().conjoin(xx1) + """ + When iterated to list + Then the result should be unordered + | result | + | 27/29/32/35 | + @GraphComputerVerificationReferenceOnly Scenario: g_V_out_path_byXvaluesXnameX_toUpperX_conjoinXMARKOX Given the modern graph diff --git a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/process/traversal/step/sideEffect/Neo4jGraphStep.java b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/process/traversal/step/sideEffect/Neo4jGraphStep.java index 7ab286543c..abd0a6b801 100644 --- a/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/process/traversal/step/sideEffect/Neo4jGraphStep.java +++ b/neo4j-gremlin/src/main/java/org/apache/tinkerpop/gremlin/neo4j/process/traversal/step/sideEffect/Neo4jGraphStep.java @@ -66,7 +66,7 @@ public final class Neo4jGraphStep<S, E extends Element> extends GraphStep<S, E> private Iterator<? extends Edge> edges() { if (null == this.ids) return Collections.emptyIterator(); - return IteratorUtils.filter(this.getTraversal().getGraph().get().edges(this.ids), edge -> HasContainer.testAll(edge, this.hasContainers)); + return IteratorUtils.filter(this.getTraversal().getGraph().get().edges(this.getResolvedIds()), edge -> HasContainer.testAll(edge, this.hasContainers)); } private Iterator<? extends Vertex> vertices() { @@ -76,7 +76,7 @@ public final class Neo4jGraphStep<S, E extends Element> extends GraphStep<S, E> // ids are present, filter on them first if (ids.length > 0) - return IteratorUtils.filter(graph.vertices(ids), vertex -> HasContainer.testAll(vertex, hasContainers)); + return IteratorUtils.filter(graph.vertices(this.getResolvedIds()), vertex -> HasContainer.testAll(vertex, hasContainers)); ////// do index lookups ////// graph.tx().readWrite(); // get a label being search on diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java index 6a3e965629..e529d78390 100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java @@ -75,7 +75,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E> if (null == this.ids) iterator = Collections.emptyIterator(); else if (this.ids.length > 0) - iterator = this.iteratorList(graph.edges(this.ids)); + iterator = this.iteratorList(graph.edges(this.getResolvedIds())); else iterator = null == indexedContainer ? this.iteratorList(graph.edges()) : @@ -97,7 +97,7 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E> if (null == this.ids) iterator = Collections.emptyIterator(); else if (this.ids.length > 0) - iterator = this.iteratorList(graph.vertices(this.ids)); + iterator = this.iteratorList(graph.vertices(this.getResolvedIds())); else iterator = (null == indexedContainer ? this.iteratorList(graph.vertices()) : diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java index 329a07fbae..52fd414164 100644 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/TinkerWorld.java @@ -109,6 +109,11 @@ public abstract class TinkerWorld implements World { public AbstractTinkerGraph open(final Configuration configuration) { return TinkerGraph.open(configuration); } + + @Override + public boolean useParametersLiterally() { + return false; + } } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
