diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java index 842d4daa322..6ef141e8e58 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/GremlinLangScriptEngine.java @@ -33,7 +33,11 @@ import javax.script.SimpleBindings; import java.io.IOException; import java.io.Reader; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; /** * A {@link GremlinScriptEngine} implementation that evaluates Gremlin scripts using {@code gremlin-language}. As it @@ -53,6 +57,8 @@ public class GremlinLangScriptEngine extends AbstractScriptEngine implements GremlinScriptEngine { private volatile GremlinScriptEngineFactory factory; + private final Function, VariableResolver> variableResolverMaker; + /** * Creates a new instance using no {@link Customizer}. */ @@ -61,6 +67,15 @@ public GremlinLangScriptEngine() { } public GremlinLangScriptEngine(final Customizer... customizers) { + final List listOfCustomizers = Arrays.asList(customizers); + + // this ScriptEngine really only supports the VariableResolverCustomizer to configure the VariableResolver + // and can't configure it more than once. first one wins + final Optional opt = listOfCustomizers.stream().filter(c -> c instanceof VariableResolverCustomizer).findFirst(); + variableResolverMaker = opt.isPresent() ? + ((VariableResolverCustomizer) opt.get()).getVariableResolverMaker() : + VariableResolver.DirectVariableResolver::new; + } @Override @@ -108,7 +123,7 @@ public Object eval(final String script, final ScriptContext context) throws Scri final Map m = context.getBindings(ScriptContext.ENGINE_SCOPE); final GremlinAntlrToJava antlr = new GremlinAntlrToJava((GraphTraversalSource) o, - new VariableResolver.DefaultVariableResolver(m)); + variableResolverMaker.apply(m)); try { return GremlinQueryParser.parse(script, antlr); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverCustomizer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverCustomizer.java new file mode 100644 index 00000000000..1be1f3d4486 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverCustomizer.java @@ -0,0 +1,40 @@ +/* + * 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.jsr223; + +import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver; + +import java.util.Map; +import java.util.function.Function; + +/** + * Supplies a {@link VariableResolver} implementation to the {@link GremlinLangScriptEngine}. This {@link Customizer} + * is not relevant to any other {@link GremlinScriptEngine} implementation. + */ +public class VariableResolverCustomizer implements Customizer { + private final Function, VariableResolver> variableResolverMaker; + + public VariableResolverCustomizer(final Function, VariableResolver> variableResolverMaker) { + this.variableResolverMaker = variableResolverMaker; + } + + public Function, VariableResolver> getVariableResolverMaker() { + return variableResolverMaker; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPlugin.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPlugin.java new file mode 100644 index 00000000000..587f9666a1e --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPlugin.java @@ -0,0 +1,95 @@ +/* + * 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.jsr223; + +import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +/** + * A plugin that allows for the configuration of a {@link VariableResolver} implementation to be used by the + * {@link GremlinLangScriptEngine}. By default, it will use the {@link VariableResolver.DirectVariableResolver} which + * directly resolves variable name to a value from the binding in the script engine context. This is the most common + * usage relevant for most users and providers. Other options are reserved for more advanced use cases. + */ +public class VariableResolverPlugin extends AbstractGremlinPlugin { + private static final String NAME = "tinkerpop.variableResolver"; + + public static final Map, VariableResolver>> VARIABLE_RESOLVERS = + new HashMap, VariableResolver>>() {{ + put(VariableResolver.DirectVariableResolver.class.getSimpleName(), VariableResolver.DirectVariableResolver::new); + put(VariableResolver.DefaultVariableResolver.class.getSimpleName(), VariableResolver.DefaultVariableResolver::new); + put(VariableResolver.NoVariableResolver.class.getSimpleName(), m -> VariableResolver.NoVariableResolver.instance()); + put(VariableResolver.NullVariableResolver.class.getSimpleName(), m -> VariableResolver.NullVariableResolver.instance()); + }}; + + private VariableResolverPlugin(final VariableResolverPlugin.Builder builder) { + super(NAME, new VariableResolverCustomizer(builder.variableResolverMaker)); + } + + /** + * Builds a set of static bindings. + */ + public static VariableResolverPlugin.Builder build() { + return new VariableResolverPlugin.Builder(); + } + + public static final class Builder { + + Function, VariableResolver> variableResolverMaker = VariableResolver.DirectVariableResolver::new; + + private Builder() {} + + /** + * Sets the type of {@link VariableResolver} to use by specifying a simple class name associated with the + * inner classes in that interface or a fully qualified class name. The assumption is that implementations + * will allow a constructor that takes a {@code Map} which contains the bindings from the script engine context. + * Implementations are can then decide how to resolve variables in the script based on that {@code Map} or some + * other mechanism. + */ + public VariableResolverPlugin.Builder resolver(final String resolverName) { + if (VARIABLE_RESOLVERS.containsKey(resolverName)) { + this.variableResolverMaker = VARIABLE_RESOLVERS.get(resolverName); + } else { + try { + // Assuming resolverName is a fully qualified class name if it's not in the simple name map + final Class clazz = Class.forName(resolverName); + if (VariableResolver.class.isAssignableFrom(clazz)) { + this.variableResolverMaker = map -> { + try { + return (VariableResolver) clazz.getConstructor(Map.class).newInstance(map); + } catch (Exception e) { + throw new RuntimeException("Error instantiating VariableResolver", e); + } + }; + } + } catch (ClassNotFoundException e) { + throw new RuntimeException("VariableResolver class not found: " + resolverName, e); + } + } + return this; + } + + public VariableResolverPlugin create() { + return new VariableResolverPlugin(this); + } + } +} 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 d5124e20b33..536dd13e555 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 @@ -23,6 +23,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.Order; 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.step.GType; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.structure.VertexProperty.Cardinality; import java.util.LinkedHashMap; @@ -445,7 +447,14 @@ public GraphTraversal visitTraversalMethod_combine_Object(final GremlinParser.Tr */ @Override public GraphTraversal visitTraversalMethod_coin(final GremlinParser.TraversalMethod_coinContext ctx) { - return graphTraversal.coin(((Number) antlr.argumentVisitor.visitFloatArgument(ctx.floatArgument())).doubleValue()); + final Object literalOrVar = antlr.argumentVisitor.visitFloatArgument(ctx.floatArgument()); + if (literalOrVar instanceof Number) + return graphTraversal.coin(((Number) literalOrVar).doubleValue()); + else if (literalOrVar instanceof GValue && ((GValue) literalOrVar).getType().isNumeric()) + return graphTraversal.asAdmin().coin((GValue) literalOrVar); + else + throw new IllegalArgumentException("coin() argument must be a double"); + } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/VariableResolver.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/VariableResolver.java index a97bd4b6467..e017808faff 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/VariableResolver.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/VariableResolver.java @@ -18,24 +18,25 @@ */ package org.apache.tinkerpop.gremlin.language.grammar; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import java.util.Map; import java.util.function.BiFunction; /** * Resolves parameters in Gremlin to objects. */ -public interface VariableResolver extends BiFunction { +public interface VariableResolver extends BiFunction { /** * This function resolves a variable name and the given parsers context to an object. */ @Override - Object apply(final String varName, final GremlinParser.VariableContext variableContext); + T apply(final String varName, final GremlinParser.VariableContext variableContext); /** * This {@link VariableResolver} implementation throws exceptions for all variable names. */ - class NoVariableResolver implements VariableResolver { + class NoVariableResolver implements VariableResolver { private static NoVariableResolver instance = new NoVariableResolver(); public static VariableResolver instance() { @@ -48,12 +49,35 @@ public Object apply(final String s, final GremlinParser.VariableContext variable } } + /** + * Allows for a provided variable set in the form of a {@code Map}, where the keys are variable names and the + * values are the parameter values to be injected into the traversal in their place. The value is provided to a + * {@link GValue} object along with the variable name for further reference. + */ + class DefaultVariableResolver implements VariableResolver> { + + private final Map variables; + + public DefaultVariableResolver(final Map variables) { + this.variables = variables; + } + + @Override + public GValue apply(final String s, final GremlinParser.VariableContext variableContext) { + if (!variables.containsKey(s)) { + throw new VariableResolverException(String.format("No variable found for %s", s)); + } + + return GValue.of(s, variables.get(s)); + } + } + /** * This {@link VariableResolver} simply provides a {@code null} value for all variable names. It's typical use * is for when you really don't intend to execute the traversal and just want to get an instance of one when * bindings are being used as with {@link NoOpTerminalVisitor}. */ - class NullVariableResolver implements VariableResolver { + class NullVariableResolver implements VariableResolver { private static NullVariableResolver instance = new NullVariableResolver(); public static VariableResolver instance() { @@ -70,11 +94,11 @@ public Object apply(final String s, final GremlinParser.VariableContext variable * Allows for a provided variable set in the form of a {@code Map}, where the keys are variable names and the * values are the parameter values to be injected into the traversal in their place. */ - class DefaultVariableResolver implements VariableResolver { + class DirectVariableResolver implements VariableResolver { private final Map variables; - public DefaultVariableResolver(final Map variables) { + public DirectVariableResolver(final Map variables) { this.variables = variables; } @@ -87,4 +111,5 @@ public Object apply(final String s, final GremlinParser.VariableContext variable return variables.get(s); } } + } \ No newline at end of file diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java index 5b88cea5fc7..862ee98bd97 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Bytecode.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.strategy.TraversalStrategyProxy; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; @@ -295,8 +296,13 @@ private final Object convertArgument(final Object argument, final boolean search if (null != variable) return new Binding<>(variable, convertArgument(argument, false)); } - // - if (argument instanceof Traversal) { + + // a GValue gets converted to its value because we don't serialize it. it really doesn't have anything to do + // with bytecode/remoting. it's an internal construct related to parsing with the grammar that is leaking + // over here. in 4.x with bytecode removed, we won't need to worry about this. + if (argument instanceof GValue) + return convertArgument(((GValue) argument).get(), false); + else if (argument instanceof Traversal) { // prevent use of "g" to spawn child traversals if (((Traversal) argument).asAdmin().getTraversalSource().isPresent()) throw new IllegalStateException(String.format( diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java index 917a13891da..56e19463689 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.process.traversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; @@ -74,7 +75,10 @@ public void setValue(final V value) { @Override public boolean test(final V testValue) { - return this.biPredicate.test(testValue, this.value); + if (this.value instanceof GValue) + return this.biPredicate.test(testValue, ((GValue) this.value).get()); + else + return this.biPredicate.test(testValue, this.value); } @Override 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 f01afa81978..3d9cd3e0882 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 @@ -47,6 +47,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.ByModulating; import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring; import org.apache.tinkerpop.gremlin.process.traversal.step.FromToModulating; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; import org.apache.tinkerpop.gremlin.process.traversal.step.ReadWriting; import org.apache.tinkerpop.gremlin.process.traversal.step.TimesModulating; @@ -229,8 +230,277 @@ */ public interface GraphTraversal extends Traversal { + /** + * Exposes administrative methods that are either internal to TinkerPop or for users with advanced needs. This + * separation helps keep the Gremlin API more concise. Any {@code GraphTraversal} can get an instance of its + * administrative form by way of {@link GraphTraversal#asAdmin()}. + *

+ * Note that step overloads allowing use of {@link GValue} objects do not construct bytecode with that object. + * Bytecode does not make use of parameterization in that fashion so it there isn't a need to really preserve that + * functionality there (doing so would require changes to serializers). + */ public interface Admin extends Traversal.Admin, GraphTraversal { + /** + * Filter the E object given a biased coin toss. For internal use for parameterization features. + * + * @param probability the probability that the object will pass through the filter + * @return the traversal with an appended {@link CoinStep}. + * @see Reference Documentation - Coin Step + * @since 3.7.3 + */ + public default GraphTraversal coin(final GValue probability) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.coin, probability); + return this.asAdmin().addStep(new CoinStep<>(this.asAdmin(), probability)); + } + + /** + * Map any object to a fixed E value. For internal use for parameterization features. + * + * @return the traversal with an appended {@link ConstantStep}. + * @see Reference Documentation - Constant Step + * @since 3.7.3 + */ + public default GraphTraversal constant(final GValue e) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.constant, e); + return this.asAdmin().addStep(new ConstantStep(this.asAdmin(), e)); + } + + /** + * Map the {@link Vertex} to its adjacent vertices given a direction and edge labels. The arguments for the + * labels must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param direction the direction to traverse from the current vertex + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal to(final Direction direction, final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.to, direction, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Vertex.class, direction, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its outgoing adjacent vertices given the edge labels. The arguments for the + * labels must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal out(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.out, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Vertex.class, Direction.OUT, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its incoming adjacent vertices given the edge labels. The arguments for the + * labels must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal in(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.in, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Vertex.class, Direction.IN, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its adjacent vertices given the edge labels. The arguments for the labels must be + * either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal both(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.both, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Vertex.class, Direction.BOTH, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its incident edges given the direction and edge labels. The arguments for the + * labels must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param direction the direction to traverse from the current vertex + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal toE(final Direction direction, final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.toE, direction, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Edge.class, direction, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its outgoing incident edges given the edge labels. The arguments for the labels + * must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal outE(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.outE, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Edge.class, Direction.OUT, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its incoming incident edges given the edge labels. The arguments for the labels + * must be either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal inE(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.inE, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Edge.class, Direction.IN, edgeLabels)); + } + + /** + * Map the {@link Vertex} to its incident edges given the edge labels. The arguments for the labels must be + * either a {@code String} or a {@link GValue}. For internal use for parameterization. + * + * @param edgeLabels the edge labels to traverse + * @return the traversal with an appended {@link VertexStep}. + * @see Reference Documentation - Vertex Step + * @since 3.7.3 + */ + public default GraphTraversal bothE(final Object... edgeLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.bothE, edgeLabels); + return this.asAdmin().addStep(new VertexStep<>(this.asAdmin(), Edge.class, Direction.BOTH, edgeLabels)); + } + + /** + * Adds a {@link Vertex}. + * + * @param vertexLabel the label of the {@link Vertex} to add + * @return the traversal with the {@link AddVertexStep} added + * @see Reference Documentation - AddVertex Step + * @since 3.7.3 + */ + public default GraphTraversal addV(final GValue vertexLabel) { + if (null == vertexLabel || null == vertexLabel.get()) throw new IllegalArgumentException("vertexLabel cannot be null"); + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.addV, vertexLabel); + return this.asAdmin().addStep(new AddVertexStep<>(this.asAdmin(), vertexLabel)); + } + + /** + * Performs 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.7.3 + */ + public default GraphTraversal mergeV(final GValue> searchCreate) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.mergeV, searchCreate); + final MergeVertexStep step = new MergeVertexStep(this.asAdmin(), false, null == searchCreate ? GValue.ofMap(null) : searchCreate); + return this.asAdmin().addStep(step); + } + + /** + * 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 3.7.3 + */ + public default GraphTraversal mergeE(final GValue> searchCreate) { + // get a construction time exception if the Map is bad + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.mergeE, searchCreate); + final MergeEdgeStep step = new MergeEdgeStep(this.asAdmin(), false, null == searchCreate ? GValue.ofMap(null) : searchCreate); + return this.asAdmin().addStep(step); + } + + /** + * Adds an {@link Edge} with the specified edge label. + * + * @param edgeLabel the label of the newly added edge + * @return the traversal with the {@link AddEdgeStep} added + * @see Reference Documentation - AddEdge Step + * @since 3.7.3 + */ + public default GraphTraversal addE(final GValue edgeLabel) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.addE, edgeLabel); + return this.asAdmin().addStep(new AddEdgeStep<>(this.asAdmin(), edgeLabel)); + } + + /** + * Filters vertices, edges and vertex properties based on their label. + * + * @param label the label of the {@link Element} + * @param otherLabels additional labels of the {@link Element} + * @return the traversal with an appended {@link HasStep} + * @see Reference Documentation - Has Step + * @since 3.2.2 + */ + public default GraphTraversal hasLabel(final Object label, final Object... otherLabels) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.hasLabel, label, otherLabels); + + // groovy evaluation seems to do strange things with varargs given hasLabel(null, null). odd someone would + // do this but the failure is ugly if not handled. + final int otherLabelsLength = null == otherLabels ? 0 : otherLabels.length; + final Object[] labels = new Object[otherLabelsLength + 1]; + labels[0] = label; + if (otherLabelsLength > 0) + System.arraycopy(otherLabels, 0, labels, 1, otherLabelsLength); + 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 m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}. + * @return the traversal with the modulated step + * @see Reference Documentation - MergeV Step + * @see Reference Documentation - MergeE Step + * @since 3.7.3 + */ + public default GraphTraversal option(final M token, final GValue> m) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.option, token, m); + ((TraversalOptionParent) this.asAdmin().getEndStep()).addChildOption(token, (Traversal.Admin) new ConstantTraversal<>(m).asAdmin()); + 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 + * the documentation of such steps to understand the usage context. + * + * @param m Provides a {@code Map} as the option which is the same as doing {@code constant(m)}. + * @return the traversal with the modulated step + * @see Reference Documentation - MergeV Step + * @see Reference Documentation - MergeE Step + * @since 3.7.3 + */ + public default GraphTraversal option(final Merge merge, final GValue> m, final VertexProperty.Cardinality cardinality) { + this.asAdmin().getBytecode().addStep(GraphTraversal.Symbols.option, merge, m, cardinality); + + final Map map = m.get(); + + // do explicit cardinality for every single pair in the map + for (Object k : map.keySet()) { + final Object o = map.get(k); + if (!(o instanceof CardinalityValueTraversal)) + map.put(k, new CardinalityValueTraversal(cardinality, o)); + } + ((TraversalOptionParent) this.asAdmin().getEndStep()).addChildOption((M) merge, (Traversal.Admin) new ConstantTraversal<>(m).asAdmin()); + return this; + } + @Override public default GraphTraversal.Admin addStep(final Step step) { return (GraphTraversal.Admin) Traversal.Admin.super.addStep((Step) step); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/ConstantTraversal.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/ConstantTraversal.java index b22d88e66c5..3bac69ce489 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/ConstantTraversal.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/lambda/ConstantTraversal.java @@ -19,6 +19,7 @@ package org.apache.tinkerpop.gremlin.process.traversal.lambda; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import java.util.Objects; @@ -29,15 +30,23 @@ */ public final class ConstantTraversal extends AbstractLambdaTraversal { - private final E end; + private final GValue end; public ConstantTraversal(final E end) { - this.end = end; + this.end = null == end ? GValue.of(null) : GValue.of(end); + } + + public ConstantTraversal(final GValue end) { + this.end = null == end ? GValue.of(null) : end; } @Override public E next() { - return this.end; + return this.end.get(); + } + + public GValue getEnd() { + return end; } @Override 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 new file mode 100644 index 00000000000..89a8b790986 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GType.java @@ -0,0 +1,81 @@ +/* + * 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; + +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * An enum that describes types that are used in the Gremlin language. + */ +public enum GType { + BIG_DECIMAL, + BIG_INTEGER, + BOOLEAN, + DOUBLE, + EDGE, + INTEGER, + LIST, + LONG, + MAP, + PATH, + PROPERTY, + SET, + STRING, + UNKNOWN, + VERTEX; + + GType() {} + + /** + * Returns true if the type is a number. + */ + public boolean isNumeric() { + return this == INTEGER || this == DOUBLE || this == LONG || this == BIG_INTEGER || this == BIG_DECIMAL; + } + + /** + * Convert an object to a matching {@link GType} and if not matched return {@link GType#UNKNOWN}. + */ + public static GType getType(final Object object) { + if (object instanceof String) return STRING; + else if (object instanceof Integer) return INTEGER; + else if (object instanceof Boolean) return BOOLEAN; + else if (object instanceof Double) return DOUBLE; + else if (object instanceof Long) return LONG; + else if (object instanceof Map) return MAP; + else if (object instanceof List) return LIST; + else if (object instanceof Set) return SET; + else if (object instanceof Vertex) return VERTEX; + else if (object instanceof Edge) return EDGE; + else if (object instanceof Path) return PATH; + else if (object instanceof Property) return PROPERTY; + else if (object instanceof BigInteger) return BIG_INTEGER; + else if (object instanceof BigDecimal) return BIG_DECIMAL; + else return 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 new file mode 100644 index 00000000000..4de2ca1f8e5 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValue.java @@ -0,0 +1,321 @@ +/* + * 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; + +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +/** + * A {@code GValue} is a variable or literal value that is used in a {@link Traversal}. It is composed of a key-value + * pair where the key is the name given to the variable and the value is the object that the variable resolved to. If + * the name is not given, the value was provided literally in the traversal. The value of the variable can be any + * object. The {@code GValue} also includes the {@link GType} that describes the type it contains. + */ +public class GValue implements Cloneable, Serializable { + private final String name; + private final GType type; + + private final V value; + + private GValue(final GType type, final V value) { + this(null, type, value); + } + + private GValue(final String name, final GType type, final V value) { + this.name = name; + this.type = type; + this.value = value; + } + + /** + * Determines if the value held by this object was defined as a variable or a literal value. Literal values simply + * have no name. + */ + public boolean isVariable() { + return this.name != null; + } + + /** + * Gets the name of the variable if it was defined as such and returns empty if the value was a literal. + */ + public Optional getName() { + return Optional.ofNullable(this.name); + } + + /** + * Gets the type of the value. The explicit type could be determined with {@code instanceof} on the value, but this + * might be helpful for cases where the value was constructed with a {@code null} value which might just return as + * {@code Object}. + */ + public GType getType() { + return this.type; + } + + /** + * Gets the value. + */ + public V get() { + return this.value; + } + + @Override + public String toString() { + return isVariable() ? + String.format("%s&%s", name, value) : Objects.toString(value); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + GValue gValue = (GValue) o; + return Objects.equals(name, gValue.name) && type == gValue.type && Objects.equals(value, gValue.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, type, value); + } + + /** + * Create a new {@code Var} from a particular value but without the specified name. + * + * @param value the value of the variable + */ + public static GValue of(final V value) { + return new GValue<>(GType.getType(value), value); + } + + /** + * Create a new {@code Var} with the specified name and value. + * + * @param name the name of the variable + * @param value the value of the variable + */ + public static GValue of(final String name, final V value) { + return new GValue<>(name, GType.getType(value), value); + } + /** + * Create a new {@code GValue} for a string value. + */ + public static GValue ofString(final String value) { + return new GValue<>(GType.STRING, value); + } + + /** + * Create a new {@code GValue} for a string value with a specified name. + */ + public static GValue ofString(final String name, final String value) { + return new GValue<>(name, GType.STRING, value); + } + + /** + * Create a new {@code GValue} for an integer value. + */ + public static GValue ofInteger(final Integer value) { + return new GValue<>(GType.INTEGER, value); + } + + /** + * Create a new {@code GValue} for an integer value with a specified name. + */ + public static GValue ofInteger(final String name, final Integer value) { + return new GValue<>(name, GType.INTEGER, value); + } + + /** + * Create a new {@code GValue} for a boolean value. + */ + public static GValue ofBoolean(final Boolean value) { + return new GValue<>(GType.BOOLEAN, value); + } + + /** + * Create a new {@code GValue} for a boolean value with a specified name. + */ + public static GValue ofBoolean(final String name, final Boolean value) { + return new GValue<>(name, GType.BOOLEAN, value); + } + + /** + * Create a new {@code GValue} for a double value. + */ + public static GValue ofDouble(final Double value) { + return new GValue<>(GType.DOUBLE, value); + } + + /** + * Create a new {@code GValue} for a double value with a specified name. + */ + public static GValue ofDouble(final String name, final Double value) { + return new GValue<>(name, GType.DOUBLE, value); + } + + /** + * Create a new {@code GValue} for a BigInteger value. + */ + public static GValue ofBigInteger(final BigInteger value) { + return new GValue<>(GType.BIG_INTEGER, value); + } + + /** + * Create a new {@code GValue} for a BigInteger value with a specified name. + */ + public static GValue ofBigInteger(final String name, final BigInteger value) { + return new GValue<>(name, GType.BIG_INTEGER, value); + } + + /** + * Create a new {@code GValue} for a BigDecimal value. + */ + public static GValue ofBigDecimal(final BigDecimal value) { + return new GValue<>(GType.BIG_DECIMAL, value); + } + + /** + * Create a new {@code GValue} for a BigDecimal value with a specified name. + */ + public static GValue ofBigDecimal(final String name, final BigDecimal value) { + return new GValue<>(name, GType.BIG_DECIMAL, value); + } + + /** + * Create a new {@code GValue} for a long value. + */ + public static GValue ofLong(final Long value) { + return new GValue<>(GType.LONG, value); + } + + /** + * Create a new {@code GValue} for a long value with a specified name. + */ + public static GValue ofLong(final String name, final Long value) { + return new GValue<>(name, GType.LONG, value); + } + + /** + * Create a new {@code GValue} for a map value. + */ + public static GValue ofMap(final Map value) { + return new GValue<>(GType.MAP, value); + } + + /** + * Create a new {@code GValue} for a map value with a specified name. + */ + public static GValue ofMap(final String name, final Map value) { + return new GValue<>(name, GType.MAP, value); + } + + /** + * Create a new {@code GValue} for a list value. + */ + public static GValue ofList(final List value) { + return new GValue<>(GType.LIST, value); + } + + /** + * Create a new {@code GValue} for a list value with a specified name. + */ + public static GValue ofList(final String name, final List value) { + return new GValue<>(name, GType.LIST, value); + } + + /** + * Create a new {@code GValue} for a set value. + */ + public static GValue ofSet(final Set value) { + return new GValue<>(GType.SET, value); + } + + /** + * Create a new {@code GValue} for a set value with a specified name. + */ + public static GValue ofSet(final String name, final Set value) { + return new GValue<>(name, GType.SET, value); + } + + /** + * Create a new {@code GValue} for a vertex value. + */ + public static GValue ofVertex(final Vertex value) { + return new GValue<>(GType.VERTEX, value); + } + + /** + * Create a new {@code GValue} for a vertex value with a specified name. + */ + public static GValue ofVertex(final String name, final Vertex value) { + return new GValue<>(name, GType.VERTEX, value); + } + + /** + * Create a new {@code GValue} for an edge value. + */ + public static GValue ofEdge(final Edge value) { + return new GValue<>(GType.EDGE, value); + } + + /** + * Create a new {@code GValue} for an edge value with a specified name. + */ + public static GValue ofEdge(final String name, final Edge value) { + return new GValue<>(name, GType.EDGE, value); + } + + /** + * Create a new {@code GValue} for a path value. + */ + public static GValue ofPath(final Path value) { + return new GValue<>(GType.PATH, value); + } + + /** + * Create a new {@code GValue} for a path value with a specified name. + */ + public static GValue ofPath(final String name, final Path value) { + return new GValue<>(name, GType.PATH, value); + } + + /** + * Create a new {@code GValue} for a property value. + */ + public static GValue ofProperty(final Property value) { + return new GValue<>(GType.PROPERTY, value); + } + + /** + * Create a new {@code GValue} for a property value with a specified name. + */ + public static GValue ofProperty(final String name, final Property value) { + return new GValue<>(name, GType.PROPERTY, 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 e4a44abe934..e2209522000 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 @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.Seedable; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; @@ -29,16 +30,27 @@ import java.util.Set; /** + * A filter step that will randomly allow traversers to pass through the step based on a probability value. + * * @author Marko A. Rodriguez (http://markorodriguez.com) */ public final class CoinStep extends FilterStep implements Seedable { private final Random random = new Random(); - private final double probability; + private final GValue probability; + /** + * @deprecated As of release 3.7.3, replaced by {@link #CoinStep(Traversal.Admin, GValue)} + */ + @Deprecated public CoinStep(final Traversal.Admin traversal, final double probability) { super(traversal); - this.probability = probability; + this.probability = GValue.ofDouble(probability); + } + + public CoinStep(final Traversal.Admin traversal, final GValue probability) { + super(traversal); + this.probability = null == probability ? GValue.ofDouble(null) : probability; } @Override @@ -46,8 +58,12 @@ public void resetSeed(final long seed) { random.setSeed(seed); } + public GValue getProbabilityGValue() { + return probability; + } + public double getProbability() { - return this.probability; + return this.probability.get(); } @Override @@ -55,11 +71,11 @@ protected boolean filter(final Traverser.Admin traverser) { long newBulk = 0l; if (traverser.bulk() < 100) { for (int i = 0; i < traverser.bulk(); i++) { - if (this.probability >= random.nextDouble()) + if (this.probability.get() >= random.nextDouble()) newBulk++; } } else { - newBulk = Math.round(this.probability * traverser.bulk()); + newBulk = Math.round(this.probability.get() * traverser.bulk()); } if (0 == newBulk) return false; traverser.setBulk(newBulk); @@ -73,7 +89,7 @@ public String toString() { @Override public int hashCode() { - return super.hashCode() ^ Double.hashCode(this.probability); + return super.hashCode() ^ Double.hashCode(this.probability.get()); } @Override diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java index f5f72807641..9dfa4ce7544 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddEdgeStep.java @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.FromToModulating; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.Scoping; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.Writing; @@ -59,6 +60,11 @@ public AddEdgeStep(final Traversal.Admin traversal, final String edgeLabel) { this.parameters.set(this, T.label, edgeLabel); } + public AddEdgeStep(final Traversal.Admin traversal, final GValue edgeLabel) { + super(traversal); + this.parameters.set(this, T.label, edgeLabel); + } + public AddEdgeStep(final Traversal.Admin traversal, final Traversal.Admin edgeLabelTraversal) { super(traversal); this.parameters.set(this, T.label, edgeLabelTraversal); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java index 80597ca86c3..3a433463e82 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/AddVertexStep.java @@ -20,6 +20,7 @@ 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.Scoping; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.step.Writing; @@ -50,6 +51,10 @@ public AddVertexStep(final Traversal.Admin traversal, final String label) { super(traversal); this.parameters.set(this, T.label, null == label ? Vertex.DEFAULT_LABEL : label); } + public AddVertexStep(final Traversal.Admin traversal, final GValue label) { + super(traversal); + this.parameters.set(this, T.label, null == label ? Vertex.DEFAULT_LABEL : label); + } public AddVertexStep(final Traversal.Admin traversal, final Traversal.Admin vertexLabelTraversal) { super(traversal); 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 9434cc3b478..bf554d1aa9e 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 @@ -20,6 +20,7 @@ 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.structure.util.StringFactory; @@ -29,29 +30,37 @@ public class ConstantStep extends ScalarMapStep { - private final E constant; + private final GValue constant; public ConstantStep(final Traversal.Admin traversal, final E constant) { + this(traversal, GValue.of(constant)); + } + + public ConstantStep(final Traversal.Admin traversal, final GValue constant) { super(traversal); - this.constant = constant; + this.constant = null == constant ? GValue.of(null) : constant; } public E getConstant() { + return this.constant.get(); + } + + public GValue getConstantGValue() { return this.constant; } @Override protected E map(final Traverser.Admin traverser) { - return this.constant; + return this.constant.get(); } @Override public String toString() { - return StringFactory.stepString(this, this.constant); + return StringFactory.stepString(this, this.constant.get()); } @Override public int hashCode() { - return super.hashCode() ^ Objects.hashCode(this.constant); + return super.hashCode() ^ Objects.hashCode(this.constant.get()); } } 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 3b1720ff9ae..ee151aed12c 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 @@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.GraphComputing; import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; @@ -66,11 +67,28 @@ public GraphStep(final Traversal.Admin traversal, final Class returnClass, fi 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; this.isStart = isStart; + this.iteratorSupplier = () -> (Iterator) (Vertex.class.isAssignableFrom(this.returnClass) ? - this.getTraversal().getGraph().get().vertices(this.ids) : - this.getTraversal().getGraph().get().edges(this.ids)); + this.getTraversal().getGraph().get().vertices(convertGValuesToIds()) : + this.getTraversal().getGraph().get().edges(convertGValuesToIds())); } + /** + * Converts {@link GValue} objects the ids array to their values to prevent them from leaking to the Graph API. + */ + 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]; + } + } + return newIds; + } + + public String toString() { return StringFactory.stepString(this, this.returnClass.getSimpleName().toLowerCase(), Arrays.toString(this.ids)); } @@ -137,7 +155,13 @@ public void onGraphComputer() { public void convertElementsToIds() { if (null != this.ids) { - for (int i = 0; i < this.ids.length; i++) { // if this is going to OLAP, convert to ids so you don't serialize elements + // 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(); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java index 6272a497a96..3204d7746f0 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeEdgeStep.java @@ -34,6 +34,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.EventUtil; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; import org.apache.tinkerpop.gremlin.structure.Direction; @@ -71,6 +72,10 @@ public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, fin super(traversal, isStart, merge); } + public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final GValue merge) { + super(traversal, isStart, merge); + } + public MergeEdgeStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin mergeTraversal) { super(traversal, isStart, mergeTraversal); } 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 9b0f12d1d63..229e507a150 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 @@ -35,6 +35,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.IdentityTraversal; import org.apache.tinkerpop.gremlin.process.traversal.step.Deleting; +import org.apache.tinkerpop.gremlin.process.traversal.step.GType; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalOptionParent; import org.apache.tinkerpop.gremlin.process.traversal.step.Writing; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters; @@ -81,6 +83,11 @@ public MergeStep(final Traversal.Admin traversal, final boolean isStart, final M validate(mergeMap, false); } + public MergeStep(final Traversal.Admin traversal, final boolean isStart, final GValue mergeMap) { + this(traversal, isStart, new ConstantTraversal<>(mergeMap)); + validate(mergeMap.get(), false); + } + public MergeStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin mergeTraversal) { super(traversal); @@ -288,7 +295,7 @@ protected static void validate(final Map map, final boolean ignoreTokens, final op, allowedTokens, k)); } if (k == T.label) { - if (!(v instanceof String)) { + if (!(isStringType(v))) { throw new IllegalArgumentException(String.format( "%s() and option(onCreate) args expect T.label value to be of String - found: %s", op, v.getClass().getSimpleName())); @@ -309,6 +316,13 @@ protected static void validate(final Map map, final boolean ignoreTokens, final }); } + /** + * Determines if the object is a string or a {@link GValue} with a string value. + */ + protected static boolean isStringType(final Object k) { + return k instanceof String || (k instanceof GValue && ((GValue) k).getType() == GType.STRING); + } + /** * Prohibit overrides to the existence criteria (id/label/from/to) in onCreate. */ @@ -369,7 +383,7 @@ protected GraphTraversal searchVerticesTraversal(final Graph graph, final Object return id != null ? graph.traversal().V(id) : graph.traversal().V(); } - protected GraphTraversal searchVerticesLabelConstraint(GraphTraversal t, final String label) { + protected GraphTraversal searchVerticesLabelConstraint(final GraphTraversal t, final String label) { return label != null ? t.hasLabel(label) : t; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java index 11a9fc2ca41..51820f0c2b2 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/MergeVertexStep.java @@ -30,6 +30,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.process.traversal.lambda.ConstantTraversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.util.event.EventUtil; import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.T; @@ -60,6 +61,10 @@ public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, f super(traversal, isStart, merge); } + public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final GValue merge) { + super(traversal, isStart, merge); + } + public MergeVertexStep(final Traversal.Admin traversal, final boolean isStart, final Traversal.Admin mergeTraversal) { super(traversal, isStart, mergeTraversal); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java index bd429f049ce..e27fbb3802c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/VertexStep.java @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.Configuring; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.util.Parameters; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; import org.apache.tinkerpop.gremlin.structure.Direction; @@ -39,20 +40,41 @@ import java.util.stream.Collectors; /** + * Handles the logic of traversing to adjacent vertices or edges given a direction and edge labels for steps like, + * {@code out}, {@code in}, {@code both}, {@code outE}, {@code inE}, and {@code bothE}. + * * @author Marko A. Rodriguez (http://markorodriguez.com) */ public class VertexStep extends FlatMapStep implements AutoCloseable, Configuring { protected Parameters parameters = new Parameters(); private final String[] edgeLabels; + private final GValue[] edgeLabelsGValue; private Direction direction; private final Class returnClass; public VertexStep(final Traversal.Admin traversal, final Class returnClass, final Direction direction, final String... edgeLabels) { + this(traversal, returnClass, direction, (Object[]) edgeLabels); + } + + public VertexStep(final Traversal.Admin traversal, final Class returnClass, final Direction direction, final Object... edgeLabels) { super(traversal); this.direction = direction; - this.edgeLabels = edgeLabels; this.returnClass = returnClass; + + // check each edgeLabel to ensure it is a string or a GValue with a GType.STRING in it. if it is just a string + // then convert edgeLabels to a GValue or otherwise throw an exception + this.edgeLabelsGValue = Arrays.stream(edgeLabels).map(edgeLabel -> { + if (edgeLabel instanceof String) + return GValue.ofString((String) edgeLabel); + else if (edgeLabel instanceof GValue && ((GValue) edgeLabel).get().getClass().equals(String.class)) + return (GValue) edgeLabel; + else + throw new IllegalArgumentException("All edge labels must be strings"); + }).toArray(GValue[]::new); + + // convert the GValue to a String[] for the edgeLabels field to cache the values + this.edgeLabels = Arrays.stream(this.edgeLabelsGValue).map(GValue::get).toArray(String[]::new); } @Override @@ -67,6 +89,9 @@ public void configure(final Object... keyValues) { @Override protected Iterator flatMap(final Traverser.Admin traverser) { + // not passing GValue to graphs at this point. if a graph wants to support GValue, it should implement + // its own step to do so. in this way, we keep things backwards compatible and don't force folks to have + // deal with this until they are ready. return Vertex.class.isAssignableFrom(this.returnClass) ? (Iterator) traverser.get().vertices(this.direction, this.edgeLabels) : (Iterator) traverser.get().edges(this.direction, this.edgeLabels); @@ -80,6 +105,10 @@ public String[] getEdgeLabels() { return this.edgeLabels; } + public GValue[] getEdgeLabelsGValue() { + return this.edgeLabelsGValue; + } + public Class getReturnClass() { return this.returnClass; } @@ -88,10 +117,16 @@ public void reverseDirection() { this.direction = this.direction.opposite(); } + /** + * Determines if the step returns vertices. + */ public boolean returnsVertex() { return this.returnClass.equals(Vertex.class); } + /** + * Determines if the step returns edges. + */ public boolean returnsEdge() { return this.returnClass.equals(Edge.class); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java index d1654a344eb..9b4a926c742 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/Parameters.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.ArrayUtils; 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.Scoping; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; @@ -165,7 +166,7 @@ public Object remove(final Object key) { /** * Gets the array of keys/values of the parameters while resolving parameter values that contain - * {@link Traversal} instances. + * {@link Traversal} or {@link GValue} instances. */ public Object[] getKeyValues(final Traverser.Admin traverser, final Object... exceptKeys) { if (this.parameters.isEmpty()) return EMPTY_ARRAY; @@ -173,14 +174,28 @@ public Object[] getKeyValues(final Traverser.Admin traverser, final Objec for (final Map.Entry> entry : this.parameters.entrySet()) { if (!ArrayUtils.contains(exceptKeys, entry.getKey())) { for (final Object value : entry.getValue()) { - keyValues.add(entry.getKey() instanceof Traversal.Admin ? TraversalUtil.apply(traverser, (Traversal.Admin) entry.getKey()) : entry.getKey()); - keyValues.add(value instanceof Traversal.Admin ? TraversalUtil.apply(traverser, (Traversal.Admin) value) : value); + keyValues.add(resolve(entry.getKey(), traverser)); + keyValues.add(resolve(value, traverser)); } } } return keyValues.toArray(new Object[keyValues.size()]); } + /** + * Takes an object and tests if it is a {@link GValue} or a {@link Traversal} and if so, resolves it to its value + * otherwise it just returns itself. + */ + private static Object resolve(final Object object, final Traverser.Admin traverser) { + if (object instanceof Traversal.Admin) { + return TraversalUtil.apply(traverser, (Traversal.Admin) object); + } else if (object instanceof GValue) { + return ((GValue) object).get(); + } else { + return object; + } + } + /** * Gets an immutable set of the parameters without evaluating them in the context of a {@link Traverser} as * is done in {@link #getKeyValues(Traverser.Admin, Object...)}. diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SubgraphStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SubgraphStrategy.java index 6f34d622262..b44b62f0e3f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SubgraphStrategy.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/decoration/SubgraphStrategy.java @@ -193,7 +193,7 @@ public void apply(final Traversal.Admin traversal) { continue; if (edgeCriterion != null) { - final VertexStep someEStep = new VertexStep<>(traversal, Edge.class, step.getDirection(), step.getEdgeLabels()); + final VertexStep someEStep = new VertexStep<>(traversal, Edge.class, step.getDirection(), step.getEdgeLabelsGValue()); final Step someVStep = step.getDirection() == Direction.BOTH ? new EdgeOtherVertexStep(traversal) : new EdgeVertexStep(traversal, step.getDirection().opposite()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/AdjacentToIncidentStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/AdjacentToIncidentStrategy.java index d8e538ab378..b33d527c2c1 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/AdjacentToIncidentStrategy.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/AdjacentToIncidentStrategy.java @@ -118,7 +118,7 @@ private static void optimizeStep(final Traversal.Admin traversal, final Step ste final Step newStep; if (step instanceof VertexStep) { final VertexStep vs = (VertexStep) step; - newStep = new VertexStep<>(traversal, Edge.class, vs.getDirection(), vs.getEdgeLabels()); + newStep = new VertexStep<>(traversal, Edge.class, vs.getDirection(), vs.getEdgeLabelsGValue()); } else if (step instanceof PropertiesStep) { final PropertiesStep ps = (PropertiesStep) step; newStep = new PropertiesStep(traversal, PropertyType.PROPERTY, ps.getPropertyKeys()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/IncidentToAdjacentStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/IncidentToAdjacentStrategy.java index 75d55adcd06..813b60b955d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/IncidentToAdjacentStrategy.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/IncidentToAdjacentStrategy.java @@ -110,7 +110,7 @@ private static boolean isOptimizable(final Step step1, final Step step2) { * @param step2 the vertex-emitting step to replace */ private static void optimizeSteps(final Traversal.Admin traversal, final VertexStep step1, final Step step2) { - final Step newStep = new VertexStep(traversal, Vertex.class, step1.getDirection(), step1.getEdgeLabels()); + final Step newStep = new VertexStep(traversal, Vertex.class, step1.getDirection(), step1.getEdgeLabelsGValue()); for (final String label : (Iterable) step2.getLabels()) { newStep.addLabel(label); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java index d2713218c53..5ff63b3e0f1 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/InlineFilterStrategy.java @@ -26,6 +26,8 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.step.GType; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaHolder; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AndStep; import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DedupGlobalStep; @@ -124,26 +126,28 @@ private static boolean processHasStep(final HasStep step, final Traversal.Adm && ((VertexStep) step.getPreviousStep()).returnsEdge() && 0 == ((VertexStep) step.getPreviousStep()).getEdgeLabels().length) { final VertexStep previousStep = (VertexStep) step.getPreviousStep(); - final List edgeLabels = new ArrayList<>(); + final List edgeLabels = new ArrayList<>(); for (final HasContainer hasContainer : new ArrayList<>(step.getHasContainers())) { if (hasContainer.getKey().equals(T.label.getAccessor())) { if (hasContainer.getBiPredicate() == Compare.eq && - hasContainer.getValue() instanceof String && + (hasContainer.getValue() instanceof String || + (hasContainer.getValue() instanceof GValue && ((GValue) hasContainer.getValue()).getType() == GType.STRING)) && edgeLabels.isEmpty()) { - edgeLabels.add((String) hasContainer.getValue()); + edgeLabels.add(hasContainer.getValue()); step.removeHasContainer(hasContainer); } else if (hasContainer.getBiPredicate() == Contains.within && hasContainer.getValue() instanceof Collection && ((Collection) hasContainer.getValue()).containsAll(edgeLabels)) { - edgeLabels.addAll((Collection) hasContainer.getValue()); + edgeLabels.addAll((Collection) hasContainer.getValue()); step.removeHasContainer(hasContainer); } else if (hasContainer.getPredicate() instanceof OrP && edgeLabels.isEmpty()) { boolean removeContainer = true; final List> orps = ((OrP) hasContainer.getPredicate()).getPredicates(); - final List newEdges = new ArrayList<>(); + final List newEdges = new ArrayList<>(); for (int i = 0; i < orps.size(); i++) { - if (orps.get(i).getBiPredicate() == Compare.eq && orps.get(i).getValue() instanceof String) - newEdges.add((String) orps.get(i).getValue()); + if (orps.get(i).getBiPredicate() == Compare.eq && (orps.get(i).getValue() instanceof String || + (orps.get(i).getValue() instanceof GValue && ((GValue) orps.get(i).getValue()).getType() == GType.STRING))) + newEdges.add(orps.get(i).getValue()); else { removeContainer = false; break; @@ -157,7 +161,7 @@ private static boolean processHasStep(final HasStep step, final Traversal.Adm } } if (!edgeLabels.isEmpty()) { - final VertexStep newVertexStep = new VertexStep<>(traversal, Edge.class, previousStep.getDirection(), edgeLabels.toArray(new String[edgeLabels.size()])); + final VertexStep newVertexStep = new VertexStep<>(traversal, Edge.class, previousStep.getDirection(), edgeLabels.toArray(new Object[edgeLabels.size()])); TraversalHelper.replaceStep(previousStep, newVertexStep, traversal); TraversalHelper.copyLabels(previousStep, newVertexStep, false); if (step.getHasContainers().isEmpty()) { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java index af7a18a1d93..962140adb55 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/VertexProperty.java @@ -18,9 +18,6 @@ */ package org.apache.tinkerpop.gremlin.structure; -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.__; import org.apache.tinkerpop.gremlin.process.traversal.lambda.CardinalityValueTraversal; import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyVertexProperty; 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 4ee9f99bf9a..9edea315121 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 @@ -38,6 +38,8 @@ 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.step.GType; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; 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; @@ -324,7 +326,7 @@ public static List> initV3Registrations() { add(GryoTypeReg.of(Bytecode.class, 122, new GryoSerializersV3.BytecodeSerializer())); add(GryoTypeReg.of(P.class, 124, new GryoSerializersV3.PSerializer())); add(GryoTypeReg.of(TextP.class, 186, new GryoSerializersV3.TextPSerializer())); - add(GryoTypeReg.of(Text.RegexPredicate.class, 197)); // ***LAST ID*** + add(GryoTypeReg.of(Text.RegexPredicate.class, 197)); add(GryoTypeReg.of(Lambda.class, 125, new GryoSerializersV3.LambdaSerializer())); add(GryoTypeReg.of(Bytecode.Binding.class, 126, new GryoSerializersV3.BindingSerializer())); add(GryoTypeReg.of(Order.class, 127)); @@ -422,6 +424,9 @@ public static List> initV3Registrations() { add(GryoTypeReg.of(Stack.class, 181)); add(GryoTypeReg.of(ReferenceMap.class, 182)); + add(GryoTypeReg.of(GValue.class, 199, new JavaSerializer())); + add(GryoTypeReg.of(GType.class, 200, new JavaSerializer())); // ***LAST ID*** + // placeholder serializers for classes that don't live here in core. this will allow them to be used if // present or ignored if the class isn't available. either way the registration numbers are held as // placeholders so that the format stays stable @@ -537,7 +542,7 @@ public static List> initV1Registrations() { add(GryoTypeReg.of(Pop.class, 133)); add(GryoTypeReg.of(SackFunctions.Barrier.class, 135)); add(GryoTypeReg.of(Pick.class, 137)); - add(GryoTypeReg.of(DT.class, 198)); // ***LAST ID*** + add(GryoTypeReg.of(DT.class, 198)); add(GryoTypeReg.of(Merge.class, 196)); add(GryoTypeReg.of(HashSetSupplier.class, 136, new UtilSerializers.HashSetSupplierSerializer())); add(GryoTypeReg.of(MultiComparator.class, 165)); @@ -628,6 +633,9 @@ public static List> initV1Registrations() { add(GryoTypeReg.of(LabelledCounter.class, 180)); add(GryoTypeReg.of(Stack.class, 181)); add(GryoTypeReg.of(ReferenceMap.class, 182)); + + add(GryoTypeReg.of(GValue.class, 199, new JavaSerializer())); + add(GryoTypeReg.of(GType.class, 200, new JavaSerializer())); // ***LAST ID*** }}; } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPluginTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPluginTest.java new file mode 100644 index 00000000000..0d3dc5fd96d --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/jsr223/VariableResolverPluginTest.java @@ -0,0 +1,117 @@ +/* + * 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.jsr223; + +import org.apache.tinkerpop.gremlin.language.grammar.GremlinParser; +import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver; +import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.fail; + +import java.util.HashMap; +import java.util.Map; + +public class VariableResolverPluginTest { + + @Test + public void shouldProduceNullVariableResolver() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver(VariableResolver.NullVariableResolver.class.getSimpleName()).create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(VariableResolver.NullVariableResolver.class)); + } + + @Test + public void shouldProduceNoVariableResolver() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver(VariableResolver.NoVariableResolver.class.getSimpleName()).create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(VariableResolver.NoVariableResolver.class)); + } + + @Test + public void shouldProduceDefaultVariableResolver() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver(VariableResolver.DefaultVariableResolver.class.getSimpleName()).create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(VariableResolver.DefaultVariableResolver.class)); + } + + @Test + public void shouldProduceDirectVariableResolver() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver(VariableResolver.DirectVariableResolver.class.getSimpleName()).create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(VariableResolver.DirectVariableResolver.class)); + } + + @Test + public void shouldProduceDefaultedVariableResolverWhenNoSpecified() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build().create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(VariableResolver.DirectVariableResolver.class)); + } + + @Test + public void shouldProduceCustomVariableResolverForFullyQualifiedClassName() { + final Map bindings = new HashMap<>(); + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver(CustomVariableResolver.class.getName()).create(); + final VariableResolver resolver = ((VariableResolverCustomizer) plugin.getCustomizers(). + get()[0]).getVariableResolverMaker().apply(bindings); + assertThat(resolver, instanceOf(CustomVariableResolver.class)); + } + + @Test + public void shouldProduceExceptionVariableResolverIfResolverNotFound() { + try { + final VariableResolverPlugin plugin = VariableResolverPlugin.build(). + resolver("NotAResolver").create(); + fail("The resolver does not exist and should have failed"); + } catch (Exception re) { + assertThat(re.getMessage(), containsString("VariableResolver class not found: NotAResolver")); + } + } + + /** + * Custom test implementation of {@link VariableResolver} that is not part of the standard set of implementations. + * This class does nothing. + */ + public static class CustomVariableResolver implements VariableResolver { + public CustomVariableResolver(final Map m) { + } + + @Override + public Object apply(final String varName, final GremlinParser.VariableContext variableContext) { + return null; + } + } +} \ No newline at end of file diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ArgumentVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ArgumentVisitorTest.java index 43f33054b56..e9c98cd2c33 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ArgumentVisitorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/ArgumentVisitorTest.java @@ -84,81 +84,81 @@ public class ArgumentVisitorTest { public static Iterable generateTestParameters() { return Arrays.asList(new Object[][]{ {Boolean.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Boolean.class, "true", true, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", true)))}, - {Boolean.class, "false", false, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", true)))}, - {Boolean.class, "x", true, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", true)))}, + {Boolean.class, "true", true, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", true)))}, + {Boolean.class, "false", false, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", true)))}, + {Boolean.class, "x", true, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", true)))}, {Integer.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Integer.class, "0", 0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100)))}, - {Integer.class, "0i", 0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100)))}, - {Integer.class, "0L", 0L, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100)))}, - {Integer.class, "x", 0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 0)))}, - {Integer.class, "x", 0L, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 0L)))}, + {Integer.class, "0", 0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100)))}, + {Integer.class, "0i", 0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100)))}, + {Integer.class, "0L", 0L, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100)))}, + {Integer.class, "x", 0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 0)))}, + {Integer.class, "x", 0L, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 0L)))}, {Float.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Float.class, "0.0d", 0.0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 1000.0)))}, - {Float.class, "0d", 0.0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 1000.0)))}, - {Float.class, "0F", 0.0F, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 1000.0F)))}, - {Float.class, "x", 0.0, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 0.0)))}, - {Float.class, "x", 0.0F, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 0.0F)))}, + {Float.class, "0.0d", 0.0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 1000.0)))}, + {Float.class, "0d", 0.0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 1000.0)))}, + {Float.class, "0F", 0.0F, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 1000.0F)))}, + {Float.class, "x", 0.0, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 0.0)))}, + {Float.class, "x", 0.0F, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 0.0F)))}, {String.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {String.class, "'test'", "test", createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {String.class, "x", "test", createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "test")))}, - {String.class, "x", "graphson", createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", IO.graphson)))}, + {String.class, "'test'", "test", createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {String.class, "x", "test", createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "test")))}, + {String.class, "x", "graphson", createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", IO.graphson)))}, {StringNullable.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {StringNullable.class, "null", null, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {StringNullable.class, "x", null, createAntlr(new VariableResolver.DefaultVariableResolver(nullMap))}, + {StringNullable.class, "null", null, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {StringNullable.class, "x", null, createAntlr(new VariableResolver.DirectVariableResolver(nullMap))}, {Object.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Object.class, "'test'", "test", createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Object.class, "x", "test", createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "test")))}, - {Object.class, "x", now, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", now)))}, - {Object.class, "[1,2,3]", Arrays.asList(1, 2, 3), createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", now)))}, - {Object.class, "x", P.eq(100), createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", P.eq(100))))}, + {Object.class, "'test'", "test", createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Object.class, "x", "test", createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "test")))}, + {Object.class, "x", now, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", now)))}, + {Object.class, "[1,2,3]", Arrays.asList(1, 2, 3), createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", now)))}, + {Object.class, "x", P.eq(100), createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", P.eq(100))))}, {Direction.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Direction.class, "Direction.OUT", Direction.OUT, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Direction.class, "OUT", Direction.OUT, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Direction.class, "x", Direction.OUT, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Direction.OUT)))}, - {Direction.class, "x", Direction.from, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Direction.from)))}, + {Direction.class, "Direction.OUT", Direction.OUT, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Direction.class, "OUT", Direction.OUT, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Direction.class, "x", Direction.OUT, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Direction.OUT)))}, + {Direction.class, "x", Direction.from, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Direction.from)))}, {Vertex.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Vertex.class, "new Vertex(1i,'person')", new ReferenceVertex(1, "person"), createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Direction.from)))}, - {Vertex.class, "x", new ReferenceVertex(1, "person"), createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", new ReferenceVertex(1, "person"))))}, + {Vertex.class, "new Vertex(1i,'person')", new ReferenceVertex(1, "person"), createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Direction.from)))}, + {Vertex.class, "x", new ReferenceVertex(1, "person"), createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", new ReferenceVertex(1, "person"))))}, {Order.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Order.class, "Order.desc", Order.desc, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Order.class, "x", Order.desc, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Order.desc)))}, + {Order.class, "Order.desc", Order.desc, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Order.class, "x", Order.desc, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Order.desc)))}, {Scope.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Scope.class, "Scope.local", Scope.local, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Scope.class, "local", Scope.local, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Scope.class, "x", Scope.local, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Scope.local)))}, + {Scope.class, "Scope.local", Scope.local, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Scope.class, "local", Scope.local, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Scope.class, "x", Scope.local, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Scope.local)))}, {T.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {T.class, "T.label", T.label, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {T.class, "label", T.label, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {T.class, "x", T.label, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", T.label)))}, + {T.class, "T.label", T.label, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {T.class, "label", T.label, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {T.class, "x", T.label, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", T.label)))}, {VertexProperty.Cardinality.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {VertexProperty.Cardinality.class, "Cardinality.list", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {VertexProperty.Cardinality.class, "list", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {VertexProperty.Cardinality.class, "x", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", VertexProperty.Cardinality.list)))}, + {VertexProperty.Cardinality.class, "Cardinality.list", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {VertexProperty.Cardinality.class, "list", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {VertexProperty.Cardinality.class, "x", VertexProperty.Cardinality.list, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", VertexProperty.Cardinality.list)))}, {DT.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {DT.class, "DT.hour", DT.hour, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {DT.class, "hour", DT.hour, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {DT.class, "x", DT.hour, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", DT.hour)))}, + {DT.class, "DT.hour", DT.hour, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {DT.class, "hour", DT.hour, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {DT.class, "x", DT.hour, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", DT.hour)))}, {Merge.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Merge.class, "Merge.onMatch", Merge.onMatch, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Merge.class, "onMatch", Merge.onMatch, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Merge.class, "x", Merge.onMatch, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Merge.onMatch)))}, + {Merge.class, "Merge.onMatch", Merge.onMatch, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Merge.class, "onMatch", Merge.onMatch, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Merge.class, "x", Merge.onMatch, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Merge.onMatch)))}, {Pop.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Pop.class, "Pop.last", Pop.last, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Pop.class, "last", Pop.last, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Pop.class, "x", Pop.last, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Pop.last)))}, + {Pop.class, "Pop.last", Pop.last, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Pop.class, "last", Pop.last, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Pop.class, "x", Pop.last, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Pop.last)))}, {Operator.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Operator.class, "Operator.sum", Operator.sum, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Operator.class, "sum", Operator.sum, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Operator.class, "x", Operator.sum, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Operator.sum)))}, + {Operator.class, "Operator.sum", Operator.sum, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Operator.class, "sum", Operator.sum, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Operator.class, "x", Operator.sum, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Operator.sum)))}, {Column.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {Column.class, "Column.keys", Column.keys, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Column.class, "keys", Column.keys, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {Column.class, "x", Column.keys, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", Column.keys)))}, + {Column.class, "Column.keys", Column.keys, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Column.class, "keys", Column.keys, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {Column.class, "x", Column.keys, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", Column.keys)))}, {SackFunctions.Barrier.class, "x", new VariableResolverException("x"), createAntlr(VariableResolver.NoVariableResolver.instance())}, - {SackFunctions.Barrier.class, "Barrier.normSack", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {SackFunctions.Barrier.class, "Barrier.normSack", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", "nope")))}, - {SackFunctions.Barrier.class, "x", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", SackFunctions.Barrier.normSack)))}, + {SackFunctions.Barrier.class, "Barrier.normSack", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {SackFunctions.Barrier.class, "Barrier.normSack", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", "nope")))}, + {SackFunctions.Barrier.class, "x", SackFunctions.Barrier.normSack, createAntlr(new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", SackFunctions.Barrier.normSack)))}, }); } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinQueryParserTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinQueryParserTest.java index 9b67393e663..8439e568780 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinQueryParserTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinQueryParserTest.java @@ -41,7 +41,7 @@ public void shouldParseEmpty() { public void shouldParseVariables() { final GremlinAntlrToJava gremlinAntlrToJava = new GremlinAntlrToJava("g", EmptyGraph.instance(), __::start, g, - new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("z", 50))); + new VariableResolver.DirectVariableResolver(ElementHelper.asMap("z", 50))); final GraphTraversal t = (GraphTraversal) GremlinQueryParser.parse("g.V().has('name',gt(z))", gremlinAntlrToJava); assertEquals(g.V().has("name", P.gt(50)).asAdmin().getBytecode(), @@ -52,7 +52,7 @@ public void shouldParseVariables() { public void shouldParseVariablesInVarargs() { final GremlinAntlrToJava gremlinAntlrToJava = new GremlinAntlrToJava("g", EmptyGraph.instance(), __::start, g, - new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100, + new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100, "y", 200, "z", 50))); @@ -73,7 +73,7 @@ public void shouldParseVariablesInVarargs() { public void shouldNotParseVariablesInList() { final GremlinAntlrToJava gremlinAntlrToJava = new GremlinAntlrToJava("g", EmptyGraph.instance(), __::start, g, - new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100, + new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100, "y", 200, "z", 50))); GremlinQueryParser.parse("g.V([x, y, 300]).has('name',gt(z))", gremlinAntlrToJava); @@ -83,7 +83,7 @@ public void shouldNotParseVariablesInList() { public void shouldNotParseVariablesWhichAreTraversalBased() { final GremlinAntlrToJava gremlinAntlrToJava = new GremlinAntlrToJava("g", EmptyGraph.instance(), __::start, g, - new VariableResolver.DefaultVariableResolver(ElementHelper.asMap("x", 100, + new VariableResolver.DirectVariableResolver(ElementHelper.asMap("x", 100, "y", 200, "z", __.out()))); GremlinQueryParser.parse("g.V([x, y, 300]).where(z)", gremlinAntlrToJava); diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GTypeTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GTypeTest.java new file mode 100644 index 00000000000..9596445860c --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GTypeTest.java @@ -0,0 +1,77 @@ +/* + * 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; + +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.Vertex; +import org.junit.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +public class GTypeTest { + + @Test + public void shouldReturnTrueForNumericTypes() { + assertThat(GType.INTEGER.isNumeric(), is(true)); + assertThat(GType.DOUBLE.isNumeric(), is(true)); + assertThat(GType.LONG.isNumeric(), is(true)); + assertThat(GType.BIG_INTEGER.isNumeric(), is(true)); + assertThat(GType.BIG_DECIMAL.isNumeric(), is(true)); + } + + @Test + public void shouldReturnFalseForNonNumericTypes() { + assertThat(GType.STRING.isNumeric(), is(false)); + assertThat(GType.BOOLEAN.isNumeric(), is(false)); + assertThat(GType.EDGE.isNumeric(), is(false)); + assertThat(GType.VERTEX.isNumeric(), is(false)); + } + + @Test + public void shouldReturnCorrectGType() { + assertEquals(GType.STRING, GType.getType("test")); + assertEquals(GType.INTEGER, GType.getType(123)); + assertEquals(GType.BOOLEAN, GType.getType(true)); + assertEquals(GType.DOUBLE, GType.getType(123.45)); + assertEquals(GType.LONG, GType.getType(123L)); + assertEquals(GType.MAP, GType.getType(Collections.emptyMap())); + assertEquals(GType.LIST, GType.getType(Collections.emptyList())); + assertEquals(GType.SET, GType.getType(Collections.emptySet())); + assertEquals(GType.VERTEX, GType.getType(mock(Vertex.class))); + assertEquals(GType.EDGE, GType.getType(mock(Edge.class))); + assertEquals(GType.PATH, GType.getType(mock(Path.class))); + assertEquals(GType.PROPERTY, GType.getType(mock(Property.class))); + assertEquals(GType.BIG_INTEGER, GType.getType(BigInteger.ONE)); + assertEquals(GType.BIG_DECIMAL, GType.getType(BigDecimal.ONE)); + } + + @Test + public void shouldReturnUnknownForUnmatchedTypes() { + assertEquals(GType.UNKNOWN, GType.getType(new Object())); + } +} \ No newline at end of file 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 new file mode 100644 index 00000000000..fcfac9b9d93 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/GValueTest.java @@ -0,0 +1,316 @@ +/* + * 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; + +import org.apache.tinkerpop.gremlin.process.traversal.Path; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.apache.tinkerpop.gremlin.structure.Property; +import org.apache.tinkerpop.gremlin.structure.Vertex; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; + +public class GValueTest { + + @Test + public void shouldCreateGValueFromValue() { + final GValue gValue = GValue.of(123); + assertEquals(123, gValue.get().intValue()); + assertEquals(GType.INTEGER, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromNameAndValue() { + final GValue gValue = GValue.of("varName", 123); + assertEquals(123, gValue.get().intValue()); + assertEquals(GType.INTEGER, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromString() { + final GValue gValue = GValue.ofString("test"); + assertEquals("test", gValue.get()); + assertEquals(GType.STRING, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromStringWithName() { + final GValue gValue = GValue.ofString("varName", "test"); + assertEquals("test", gValue.get()); + assertEquals(GType.STRING, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromInteger() { + final GValue gValue = GValue.ofInteger(123); + assertEquals(123, gValue.get().intValue()); + assertEquals(GType.INTEGER, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromIntegerWithName() { + final GValue gValue = GValue.ofInteger("varName", 123); + assertEquals(123, gValue.get().intValue()); + assertEquals(GType.INTEGER, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromBoolean() { + final GValue gValue = GValue.ofBoolean(true); + assertEquals(true, gValue.get()); + assertEquals(GType.BOOLEAN, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromBooleanWithName() { + final GValue gValue = GValue.ofBoolean("varName", true); + assertEquals(true, gValue.get()); + assertEquals(GType.BOOLEAN, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromDouble() { + final GValue gValue = GValue.ofDouble(123.45); + assertEquals(123.45, gValue.get(), 0.0); + assertEquals(GType.DOUBLE, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromDoubleWithName() { + final GValue gValue = GValue.ofDouble("varName", 123.45); + assertEquals(123.45, gValue.get(), 0.0); + assertEquals(GType.DOUBLE, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromBigInteger() { + final GValue gValue = GValue.ofBigInteger(BigInteger.ONE); + assertEquals(BigInteger.ONE, gValue.get()); + assertEquals(GType.BIG_INTEGER, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromBigIntegerWithName() { + final GValue gValue = GValue.ofBigInteger("varName", BigInteger.ONE); + assertEquals(BigInteger.ONE, gValue.get()); + assertEquals(GType.BIG_INTEGER, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromBigDecimal() { + final GValue gValue = GValue.ofBigDecimal(BigDecimal.ONE); + assertEquals(BigDecimal.ONE, gValue.get()); + assertEquals(GType.BIG_DECIMAL, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromBigDecimalWithName() { + final GValue gValue = GValue.ofBigDecimal("varName", BigDecimal.ONE); + assertEquals(BigDecimal.ONE, gValue.get()); + assertEquals(GType.BIG_DECIMAL, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromLong() { + final GValue gValue = GValue.ofLong(123L); + assertEquals(123L, gValue.get().longValue()); + assertEquals(GType.LONG, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromLongWithName() { + final GValue gValue = GValue.ofLong("varName", 123L); + assertEquals(123L, gValue.get().longValue()); + assertEquals(GType.LONG, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromMap() { + final Map map = new HashMap() {{ + put("key", "value"); + }}; + final GValue gValue = GValue.ofMap(map); + assertEquals(map, gValue.get()); + assertEquals(GType.MAP, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromMapWithName() { + final Map map = new HashMap() {{ + put("key", "value"); + }}; + final GValue gValue = GValue.ofMap("varName", map); + assertEquals(map, gValue.get()); + assertEquals(GType.MAP, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromList() { + final List list = Arrays.asList("value1", "value2"); + final GValue gValue = GValue.ofList(list); + assertEquals(list, gValue.get()); + assertEquals(GType.LIST, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromListWithName() { + final List list = Arrays.asList("value1", "value2"); + final GValue gValue = GValue.ofList("varName", list); + assertEquals(list, gValue.get()); + assertEquals(GType.LIST, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromSet() { + final Set set = new HashSet<>(Arrays.asList("value1", "value2")); + final GValue gValue = GValue.ofSet(set); + assertEquals(set, gValue.get()); + assertEquals(GType.SET, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromSetWithName() { + final Set set = new HashSet<>(Arrays.asList("value1", "value2")); + final GValue gValue = GValue.ofSet("varName", set); + assertEquals(set, gValue.get()); + assertEquals(GType.SET, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromVertex() { + final Vertex vertex = mock(Vertex.class); + final GValue gValue = GValue.ofVertex(vertex); + assertEquals(vertex, gValue.get()); + assertEquals(GType.VERTEX, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromVertexWithName() { + final Vertex vertex = mock(Vertex.class); + final GValue gValue = GValue.ofVertex("varName", vertex); + assertEquals(vertex, gValue.get()); + assertEquals(GType.VERTEX, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromEdge() { + final Edge edge = mock(Edge.class); + final GValue gValue = GValue.ofEdge(edge); + assertEquals(edge, gValue.get()); + assertEquals(GType.EDGE, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromEdgeWithName() { + final Edge edge = mock(Edge.class); + final GValue gValue = GValue.ofEdge("varName", edge); + assertEquals(edge, gValue.get()); + assertEquals(GType.EDGE, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromPath() { + final Path path = mock(Path.class); + final GValue gValue = GValue.ofPath(path); + assertEquals(path, gValue.get()); + assertEquals(GType.PATH, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromPathWithName() { + final Path path = mock(Path.class); + final GValue gValue = GValue.ofPath("varName", path); + assertEquals(path, gValue.get()); + assertEquals(GType.PATH, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } + + @Test + public void shouldCreateGValueFromProperty() { + final Property property = mock(Property.class); + final GValue gValue = GValue.ofProperty(property); + assertEquals(property, gValue.get()); + assertEquals(GType.PROPERTY, gValue.getType()); + assertThat(gValue.isVariable(), is(false)); + } + + @Test + public void shouldCreateGValueFromPropertyWithName() { + final Property property = mock(Property.class); + final GValue gValue = GValue.ofProperty("varName", property); + assertEquals(property, gValue.get()); + assertEquals(GType.PROPERTY, gValue.getType()); + assertEquals("varName", gValue.getName().get()); + assertThat(gValue.isVariable(), is(true)); + } +} \ No newline at end of file diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java index 27d8e44e169..dd36f4c8b5e 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/ParametersTest.java @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; import org.junit.Test; @@ -332,4 +333,14 @@ public void shouldIntegrateTraversals() { verify(mock).integrateChild(__.outE("knows").asAdmin()); } + + @Test + public void shouldGetKeyValuesAndResolveGValues() { + final Parameters parameters = new Parameters(); + parameters.set(null, "a", "axe", "b", GValue.of("B", "bat"), "c", GValue.of("C", "cat")); + + final Object[] params = parameters.getKeyValues(mock(Traverser.Admin.class)); + assertEquals(6, params.length); + assertThat(Arrays.equals(new Object[] {"a", "axe", "b", "bat", "c", "cat"}, params), is(true)); + } } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java index d799fd2ddfc..ad7f10635eb 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/translator/JavascriptTranslatorTest.java @@ -21,6 +21,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.GraphOp; import org.apache.tinkerpop.gremlin.process.traversal.Order; +import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Pop; import org.apache.tinkerpop.gremlin.process.traversal.Scope; import org.apache.tinkerpop.gremlin.process.traversal.TextP; @@ -110,6 +111,13 @@ public void shouldTranslatePop() { assertTranslation("Pop.last", Pop.last); } + @Test + public void shouldTranslateP() { + assertTranslation("P.eq(\"ark\")", P.eq("ark")); + assertTranslation("P.within([\"ark\"])", P.within("ark")); + assertTranslation("P.neq(\"ark\")", P.neq("ark")); + } + @Test public void shouldTranslateTextP() { assertTranslation("TextP.containing(\"ark\")", TextP.containing("ark")); diff --git a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java index 3560027d2f0..d853acfaee9 100644 --- a/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java +++ b/tinkergraph-gremlin/src/test/java/org/apache/tinkerpop/gremlin/tinkergraph/structure/TinkerGraphPlayTest.java @@ -18,8 +18,12 @@ */ package org.apache.tinkerpop.gremlin.tinkergraph.structure; +import org.apache.tinkerpop.gremlin.jsr223.GremlinLangScriptEngine; +import org.apache.tinkerpop.gremlin.jsr223.VariableResolverCustomizer; +import org.apache.tinkerpop.gremlin.language.grammar.VariableResolver; import org.apache.tinkerpop.gremlin.process.computer.Computer; import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.Scope; 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; @@ -39,6 +43,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.script.Bindings; import java.util.Arrays; import java.util.List; import java.util.function.BiFunction; @@ -273,17 +278,25 @@ public void testPlay6() throws Exception { } @Test - @Ignore - public void testBugs() { + public void testBugs() throws Exception { final GraphTraversalSource g = TinkerFactory.createModern().traversal(); - Object o1 = g.V().map(__.V(1)); - System.out.println(g.V().as("a").both().as("b").dedup("a", "b").by(T.label).select("a", "b").explain()); - System.out.println(g.V().as("a").both().as("b").dedup("a", "b").by(T.label).select("a", "b").toList()); - - Traversal t = - g.V("3"). - union(__.repeat(out().simplePath()).times(2).count(), - __.repeat(in().simplePath()).times(2).count()); + final GremlinLangScriptEngine se = new GremlinLangScriptEngine( + new VariableResolverCustomizer(VariableResolver.DefaultVariableResolver::new)); + + final Bindings b = se.createBindings(); + b.put("g", g); + System.out.println(((GraphTraversal) se.eval("g.V().coin(0.5).count()", b)).toList()); + + b.clear(); + b.put("g", g); + b.put("x", 0.5d); + System.out.println(((GraphTraversal) se.eval("g.V().coin(x).count()", b)).toList()); + + b.clear(); + b.put("g", g); + b.put("x", "josh"); + b.put("y", 32); + System.out.println(((GraphTraversal) se.eval("g.V().has('name', x).has('age', y).count()", b)).toList()); } @Test