This is an automated email from the ASF dual-hosted git repository. xiazcy pushed a commit to branch steps-taking-traversal-poc in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 433c2e4e3ebb9e3370b6eb9f98a22dc3de0b5e62 Author: Yang Xia <[email protected]> AuthorDate: Wed May 27 12:10:39 2026 -0700 add compile time check & strategy verification to prevent child traversal mutating steps --- .../tinkerpop/gremlin/process/traversal/P.java | 15 ++ .../tinkerpop/gremlin/process/traversal/TextP.java | 7 + .../process/traversal/TraversalStrategies.java | 2 + .../traversal/dsl/graph/GraphTraversal.java | 7 + .../traversal/dsl/graph/GraphTraversalSource.java | 3 + .../traversal/step/sideEffect/AddPropertyStep.java | 9 + .../ChildTraversalVerificationStrategy.java | 90 +++++++ .../traversal/util/ChildTraversalContext.java | 42 ++++ .../traversal/util/ChildTraversalValidator.java | 111 +++++++++ .../ChildTraversalVerificationStrategyTest.java | 69 ++++++ .../util/ChildTraversalValidatorTest.java | 228 +++++++++++++++++ .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 16 ++ gremlin-go/driver/cucumber/gremlin.go | 16 ++ .../gremlin-javascript/test/cucumber/gremlin.js | 16 ++ .../src/main/python/tests/feature/gremlin.py | 16 ++ .../gremlin/language/translator/translations.json | 272 +++++++++++++++++++++ .../filter/ChildTraversalVerification.feature | 202 +++++++++++++++ 17 files changed, 1121 insertions(+) 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 24b7016ae1..80779cd56a 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 @@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; import org.apache.tinkerpop.gremlin.structure.util.CloseableIterator; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; +import org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator; import java.io.Serializable; import java.util.ArrayList; @@ -725,6 +726,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> eq(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.eq, traversalValue.asAdmin()); } @@ -734,6 +736,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> neq(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.neq, traversalValue.asAdmin()); } @@ -743,6 +746,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> lt(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.lt, traversalValue.asAdmin()); } @@ -752,6 +756,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> lte(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.lte, traversalValue.asAdmin()); } @@ -761,6 +766,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> gt(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.gt, traversalValue.asAdmin()); } @@ -770,6 +776,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> gte(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Compare.gte, traversalValue.asAdmin()); } @@ -779,6 +786,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> within(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Contains.within, traversalValue.asAdmin()); } @@ -797,6 +805,9 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { traversals.add(tv.asAdmin()); } } + for (final Traversal.Admin<?, ?> tv : traversals) { + ChildTraversalValidator.validateFilterContext(tv); + } return new P(Contains.within, traversals); } @@ -806,6 +817,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { * @since 4.0.0 */ public static <V> P<V> without(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new P(Contains.without, traversalValue.asAdmin()); } @@ -824,6 +836,9 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { traversals.add(tv.asAdmin()); } } + for (final Traversal.Admin<?, ?> tv : traversals) { + ChildTraversalValidator.validateFilterContext(tv); + } return new P(Contains.without, traversals); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java index 8ec80a7c62..dcbe70a0cd 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.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.util.ChildTraversalValidator; import java.util.Collection; import java.util.Map; @@ -92,6 +93,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP startingWith(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.startingWith, traversalValue.asAdmin()); } @@ -119,6 +121,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP notStartingWith(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.notStartingWith, traversalValue.asAdmin()); } @@ -146,6 +149,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP endingWith(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.endingWith, traversalValue.asAdmin()); } @@ -173,6 +177,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP notEndingWith(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.notEndingWith, traversalValue.asAdmin()); } @@ -200,6 +205,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP containing(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.containing, traversalValue.asAdmin()); } @@ -227,6 +233,7 @@ public class TextP extends P<String> { * @since 4.0.0 */ public static TextP notContaining(final Traversal<?, ?> traversalValue) { + ChildTraversalValidator.validateFilterContext(traversalValue.asAdmin()); return new TextP(Text.notContaining, traversalValue.asAdmin()); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java index bccb983665..e3f0d90ff6 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TraversalStrategies.java @@ -52,6 +52,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.Path import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.PathRetractionStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.ProductiveByStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization.RepeatUnrollStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ChildTraversalVerificationStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.ComputerVerificationStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.EdgeLabelVerificationStrategy; import org.apache.tinkerpop.gremlin.process.traversal.strategy.verification.LambdaRestrictionStrategy; @@ -287,6 +288,7 @@ public interface TraversalStrategies extends Serializable, Cloneable, Iterable<T LazyBarrierStrategy.instance(), ProfileStrategy.instance(), StandardVerificationStrategy.instance(), + ChildTraversalVerificationStrategy.instance(), GValueReductionStrategy.instance()); registerStrategies(Graph.class, graphStrategies); registerStrategies(EmptyGraph.class, new DefaultTraversalStrategies()); 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 97129c4ba0..8364cb8332 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 @@ -209,6 +209,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.util.WithOptions; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.TraverserSet; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalMetrics; +import org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator; import org.apache.tinkerpop.gremlin.structure.Column; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; @@ -426,6 +427,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, Vertex> V(final Traversal<?, ?> traversal) { if (null == traversal) return V(new Object[]{ null }); + ChildTraversalValidator.validateLookupContext(traversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.V, traversal); return this.asAdmin().addStep(new GraphStep<>(this.asAdmin(), Vertex.class, false, traversal.asAdmin())); } @@ -465,6 +467,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, Edge> E(final Traversal<?, ?> traversal) { if (null == traversal) return E(new Object[]{ null }); + ChildTraversalValidator.validateLookupContext(traversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.E, traversal); return this.asAdmin().addStep(new GraphStep<>(this.asAdmin(), Edge.class, false, traversal.asAdmin())); } @@ -2715,6 +2718,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, E> has(final String propertyKey, final Traversal<?, ?> traversal) { if (null == traversal) return has(propertyKey, (Object) null); + ChildTraversalValidator.validateFilterContext(traversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.has, propertyKey, traversal); final HasContainer hasContainer = new HasContainer(propertyKey, traversal.asAdmin()); return this.asAdmin().addStep(new HasStep(this.asAdmin(), hasContainer)); @@ -2735,6 +2739,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { if (null == accessor) throw new IllegalArgumentException("The T accessor value of has(T,Traversal) cannot be null"); if (null == traversal) return has(accessor, (Object) null); + ChildTraversalValidator.validateFilterContext(traversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.has, accessor, traversal); final HasContainer hasContainer = new HasContainer(accessor.getAccessor(), traversal.asAdmin()); @@ -2787,6 +2792,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, E> has(final String label, final String propertyKey, final Traversal<?, ?> traversal) { if (null == traversal) return has(label, propertyKey, (Object) null); + ChildTraversalValidator.validateFilterContext(traversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.has, label, propertyKey, traversal); TraversalHelper.addHasContainer(this.asAdmin(), new HasContainer(T.label.getAccessor(), P.eq(label))); final HasContainer hasContainer = new HasContainer(propertyKey, traversal.asAdmin()); @@ -3936,6 +3942,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> { */ public default GraphTraversal<S, E> property(final Traversal<?, ?> mapTraversal) { if (null == mapTraversal) return this; + ChildTraversalValidator.validateMutationContext(mapTraversal.asAdmin()); this.asAdmin().getGremlinLang().addStep(Symbols.property, mapTraversal); this.asAdmin().addStep(new AddPropertyStepPlaceholder(this.asAdmin(), null, "~traversalMap", mapTraversal.asAdmin())); return this; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java index b0991a67b9..96a9ae7980 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversalSource.java @@ -42,6 +42,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.InjectStep import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStepContract; import org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.RequirementsStrategy; import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement; +import org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator; import org.apache.tinkerpop.gremlin.structure.Direction; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Graph; @@ -548,6 +549,7 @@ public class GraphTraversalSource implements TraversalSource { */ public GraphTraversal<Vertex, Vertex> V(final Traversal<?, ?> traversal) { if (null == traversal) return V(new Object[]{ null }); + ChildTraversalValidator.validateLookupContext(traversal.asAdmin()); final GraphTraversalSource clone = this.clone(); clone.gremlinLang.addStep(GraphTraversal.Symbols.V, traversal); final GraphTraversal.Admin<Vertex, Vertex> traversalAdmin = new DefaultGraphTraversal<>(clone); @@ -586,6 +588,7 @@ public class GraphTraversalSource implements TraversalSource { */ public GraphTraversal<Edge, Edge> E(final Traversal<?, ?> traversal) { if (null == traversal) return E(new Object[]{ null }); + ChildTraversalValidator.validateLookupContext(traversal.asAdmin()); final GraphTraversalSource clone = this.clone(); clone.gremlinLang.addStep(GraphTraversal.Symbols.E, traversal); final GraphTraversal.Admin<Edge, Edge> traversalAdmin = new DefaultGraphTraversal<>(clone); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java index bc83ca4b0c..0c7f30c919 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/AddPropertyStep.java @@ -148,6 +148,15 @@ public class AddPropertyStep<S extends Element> extends SideEffectStep<S> implem return; } + // Runtime validation for property(traversal) — the Map-producing form uses sentinel key "~traversalMap". + // If the traversal did NOT produce a Map, reject it to prevent the sentinel key from leaking as a real property. + if ("~traversalMap".equals(key)) { + final Object result = results.get(0); + throw new IllegalArgumentException( + "property(traversal) requires the traversal to produce a Map, but got: " + + (result == null ? "null" : result.getClass().getSimpleName())); + } + // Multi-result handling with cardinality awareness if (results.size() > 1) { // Determine effective cardinality diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java new file mode 100644 index 0000000000..95ba0cc642 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategy.java @@ -0,0 +1,90 @@ +/* + * 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.strategy.verification; + +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.TraversalParent; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AllStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.AnyStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.HasStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.IsStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.NoneStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; +import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStepContract; +import org.apache.tinkerpop.gremlin.process.traversal.strategy.AbstractTraversalStrategy; +import org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalContext; +import org.apache.tinkerpop.gremlin.process.traversal.util.ChildTraversalValidator; + +/** + * A verification strategy that validates child traversals do not contain disallowed mutating steps + * based on the context in which they are used. This serves as a safety net for cases where + * construction-time validation is bypassed (e.g., programmatic traversal construction). + * <p> + * Context classification: + * <ul> + * <li>{@code HasStep}, {@code IsStep}, {@code AllStep}, {@code AnyStep}, {@code NoneStep} → FILTER</li> + * <li>{@code GraphStep} with idTraversal → LOOKUP</li> + * <li>{@code AddPropertyStepContract} → MUTATION</li> + * </ul> + */ +public final class ChildTraversalVerificationStrategy + extends AbstractTraversalStrategy<TraversalStrategy.VerificationStrategy> + implements TraversalStrategy.VerificationStrategy { + + private static final ChildTraversalVerificationStrategy INSTANCE = new ChildTraversalVerificationStrategy(); + + private ChildTraversalVerificationStrategy() { + } + + @Override + public void apply(final Traversal.Admin<?, ?> traversal) { + for (final Step<?, ?> step : traversal.getSteps()) { + if (step instanceof TraversalParent) { + final ChildTraversalContext context = classifyContext(step); + if (context != ChildTraversalContext.NONE) { + for (final Traversal.Admin<?, ?> child : ((TraversalParent) step).getLocalChildren()) { + try { + ChildTraversalValidator.validateRecursive(child, context); + } catch (final IllegalArgumentException e) { + throw new VerificationException(e.getMessage(), traversal); + } + } + } + } + } + } + + private ChildTraversalContext classifyContext(final Step<?, ?> step) { + if (step instanceof HasStep || step instanceof IsStep || + step instanceof AllStep || step instanceof AnyStep || step instanceof NoneStep) { + return ChildTraversalContext.FILTER; + } else if (step instanceof GraphStep && ((GraphStep<?, ?>) step).getIdTraversal() != null) { + return ChildTraversalContext.LOOKUP; + } else if (step instanceof AddPropertyStepContract) { + return ChildTraversalContext.MUTATION; + } + return ChildTraversalContext.NONE; + } + + public static ChildTraversalVerificationStrategy instance() { + return INSTANCE; + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java new file mode 100644 index 0000000000..fbb489813f --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalContext.java @@ -0,0 +1,42 @@ +/* + * 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.util; + +/** + * Classifies the context in which a child traversal is used, determining what validation rules apply. + * + * <ul> + * <li>{@link #FILTER} — child traversals inside {@code has()}, {@code is()}, {@code all()}, {@code any()}, + * {@code none()}, or {@code choose()} predicates. No {@link org.apache.tinkerpop.gremlin.process.traversal.step.Mutating} + * steps are allowed.</li> + * <li>{@link #LOOKUP} — child traversals inside {@code V(traversal)} or {@code E(traversal)}. No + * {@link org.apache.tinkerpop.gremlin.process.traversal.step.Mutating} steps are allowed.</li> + * <li>{@link #MUTATION} — child traversals inside {@code property(traversal)}. Only + * {@link org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropStep} is blocked; other mutating + * steps are permitted because the traversal intentionally produces values from graph state.</li> + * <li>{@link #NONE} — not a child-traversal-bearing step, or a step whose children are not validated + * (e.g., {@code mergeV}, {@code mergeE}).</li> + * </ul> + */ +public enum ChildTraversalContext { + FILTER, + LOOKUP, + MUTATION, + NONE +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java new file mode 100644 index 0000000000..95c422eabe --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidator.java @@ -0,0 +1,111 @@ +/* + * 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.util; + +import org.apache.tinkerpop.gremlin.process.traversal.Step; +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.step.Mutating; +import org.apache.tinkerpop.gremlin.process.traversal.step.TraversalParent; +import org.apache.tinkerpop.gremlin.process.traversal.step.filter.DropStep; + +/** + * Validates child traversals to ensure they do not contain disallowed steps based on the context + * in which they are used. This utility is shared between construction-time validation (in API methods) + * and strategy-time validation ({@code ChildTraversalVerificationStrategy}). + * <p> + * Validation rules: + * <ul> + * <li><b>FILTER / LOOKUP context:</b> No steps implementing {@link Mutating} are allowed at any nesting depth.</li> + * <li><b>MUTATION context:</b> Only {@link DropStep} is blocked; other mutating steps are permitted.</li> + * </ul> + * <p> + * Recursion walks both {@link TraversalParent#getLocalChildren()} and + * {@link TraversalParent#getGlobalChildren()} to detect mutations nested inside + * {@code map()}, {@code union()}, {@code choose()}, {@code coalesce()}, or any other parent step. + */ +public final class ChildTraversalValidator { + + private ChildTraversalValidator() { + // static utility + } + + /** + * Validates a child traversal used in filter context (has, is, all, any, none, choose predicate). + * Throws {@link IllegalArgumentException} if any {@link Mutating} step is found. + */ + public static void validateFilterContext(final Traversal.Admin<?, ?> child) { + validateRecursive(child, ChildTraversalContext.FILTER); + } + + /** + * Validates a child traversal used in lookup context (V(traversal), E(traversal)). + * Throws {@link IllegalArgumentException} if any {@link Mutating} step is found. + */ + public static void validateLookupContext(final Traversal.Admin<?, ?> child) { + validateRecursive(child, ChildTraversalContext.LOOKUP); + } + + /** + * Validates a child traversal used in mutation context (property(traversal)). + * Throws {@link IllegalArgumentException} if a {@link DropStep} is found. + */ + public static void validateMutationContext(final Traversal.Admin<?, ?> child) { + validateRecursive(child, ChildTraversalContext.MUTATION); + } + + /** + * Recursively validates all steps in the child traversal and its nested children. + */ + public static void validateRecursive(final Traversal.Admin<?, ?> child, + final ChildTraversalContext context) { + for (final Step<?, ?> step : child.getSteps()) { + validateStep(step, context); + if (step instanceof TraversalParent) { + for (final Traversal.Admin<?, ?> nested : ((TraversalParent) step).getLocalChildren()) { + validateRecursive(nested, context); + } + for (final Traversal.Admin<?, ?> nested : ((TraversalParent) step).getGlobalChildren()) { + validateRecursive(nested, context); + } + } + } + } + + private static void validateStep(final Step<?, ?> step, final ChildTraversalContext context) { + switch (context) { + case FILTER: + case LOOKUP: + if (step instanceof Mutating) { + throw new IllegalArgumentException( + "Child traversal in " + context.name().toLowerCase() + " context contains mutating step " + + step.getClass().getSimpleName() + ". Mutating steps are not allowed in this context."); + } + break; + case MUTATION: + if (step instanceof DropStep) { + throw new IllegalArgumentException( + "Child traversal in mutation context contains DropStep. " + + "Destructive steps are not allowed inside property(traversal)."); + } + break; + default: + break; + } + } +} diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java new file mode 100644 index 0000000000..feb0688283 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/verification/ChildTraversalVerificationStrategyTest.java @@ -0,0 +1,69 @@ +/* + * 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.strategy.verification; + +import org.apache.tinkerpop.gremlin.process.traversal.Traversal; +import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategies; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.Graph; +import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; +import org.junit.Test; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +/** + * Tests for the ChildTraversalVerificationStrategy (strategy-time layer). + */ +public class ChildTraversalVerificationStrategyTest { + + private final GraphTraversalSource g = EmptyGraph.instance().traversal(); + + @Test + public void shouldBeRegisteredByDefault() { + // The strategy should be present in the default Graph strategy set (not EmptyGraph which has none) + assertTrue(TraversalStrategies.GlobalCache + .getStrategies(Graph.class) + .getStrategy(ChildTraversalVerificationStrategy.class).isPresent()); + } + + @Test + public void shouldBeRemovableViaWithoutStrategies() { + // Users should be able to remove the strategy if needed + final GraphTraversalSource gNoVerify = g.withoutStrategies(ChildTraversalVerificationStrategy.class); + assertFalse(gNoVerify.getStrategies().getStrategy(ChildTraversalVerificationStrategy.class).isPresent()); + } + + @Test + public void shouldNotRejectValidReadOnlyTraversal() { + // Valid traversal should pass strategy application without error + final Traversal.Admin<?, ?> traversal = g.V().has("name", __.V().values("name")).asAdmin(); + traversal.applyStrategies(); + // If we get here without exception, the test passes + } + + @Test + public void shouldNotRejectValidLookupTraversal() { + // Valid V(traversal) should pass + final Traversal.Admin<?, ?> traversal = g.V().V(__.out("knows").id()).asAdmin(); + traversal.applyStrategies(); + } +} diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java new file mode 100644 index 0000000000..e3b385dd48 --- /dev/null +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/util/ChildTraversalValidatorTest.java @@ -0,0 +1,228 @@ +/* + * 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.util; + +import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.TextP; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; +import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; +import org.apache.tinkerpop.gremlin.structure.util.empty.EmptyGraph; +import org.junit.Test; + +import static org.junit.Assert.assertThrows; + +/** + * Tests for construction-time child traversal validation. + */ +public class ChildTraversalValidatorTest { + + private final GraphTraversalSource g = EmptyGraph.instance().traversal(); + + // ===== FILTER CONTEXT: should reject mutating steps ===== + + @Test + public void shouldRejectAddVInHasTraversal() { + assertThrows(IllegalArgumentException.class, () -> + g.V().has("name", __.addV("x").values("name"))); + } + + @Test + public void shouldRejectDropInHasTraversal() { + assertThrows(IllegalArgumentException.class, () -> + g.V().has("name", __.V().drop().constant("x"))); + } + + @Test + public void shouldRejectNestedMutatingInHasTraversal() { + assertThrows(IllegalArgumentException.class, () -> + g.V().has("name", __.V().map(__.addV("x")).values("name"))); + } + + @Test + public void shouldRejectMutatingInHasWithTAccessor() { + assertThrows(IllegalArgumentException.class, () -> + g.V().has(org.apache.tinkerpop.gremlin.structure.T.label, __.addV("x").label())); + } + + @Test + public void shouldRejectMutatingInHasWithLabel() { + assertThrows(IllegalArgumentException.class, () -> + g.V().has("person", "name", __.addV("x").values("name"))); + } + + // ===== LOOKUP CONTEXT: should reject mutating steps ===== + + @Test + public void shouldRejectAddVInMidTraversalV() { + assertThrows(IllegalArgumentException.class, () -> + g.V().V(__.addV("x").id())); + } + + @Test + public void shouldRejectAddVInMidTraversalE() { + assertThrows(IllegalArgumentException.class, () -> + g.V().E(__.addV("x").id())); + } + + @Test + public void shouldRejectAddVInStartStepV() { + assertThrows(IllegalArgumentException.class, () -> + g.V(__.addV("x").id())); + } + + @Test + public void shouldRejectAddVInStartStepE() { + assertThrows(IllegalArgumentException.class, () -> + g.E(__.addV("x").id())); + } + + // ===== P FACTORY METHODS: should reject mutating steps ===== + + @Test + public void shouldRejectMutatingInPEq() { + assertThrows(IllegalArgumentException.class, () -> + P.eq(__.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInPNeq() { + assertThrows(IllegalArgumentException.class, () -> + P.neq(__.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInPGt() { + assertThrows(IllegalArgumentException.class, () -> + P.gt(__.addV("x").values("age"))); + } + + @Test + public void shouldRejectMutatingInPLt() { + assertThrows(IllegalArgumentException.class, () -> + P.lt(__.addV("x").values("age"))); + } + + @Test + public void shouldRejectMutatingInPGte() { + assertThrows(IllegalArgumentException.class, () -> + P.gte(__.addV("x").values("age"))); + } + + @Test + public void shouldRejectMutatingInPLte() { + assertThrows(IllegalArgumentException.class, () -> + P.lte(__.addV("x").values("age"))); + } + + @Test + public void shouldRejectMutatingInPWithin() { + assertThrows(IllegalArgumentException.class, () -> + P.within(__.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInPWithout() { + assertThrows(IllegalArgumentException.class, () -> + P.without(__.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInMultiTraversalWithin() { + assertThrows(IllegalArgumentException.class, () -> + P.within(__.V().values("name"), __.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInMultiTraversalWithout() { + assertThrows(IllegalArgumentException.class, () -> + P.without(__.V().values("name"), __.addV("x").values("name"))); + } + + // ===== TextP FACTORY METHODS: should reject mutating steps ===== + + @Test + public void shouldRejectMutatingInTextPStartingWith() { + assertThrows(IllegalArgumentException.class, () -> + TextP.startingWith(__.addV("x").values("name"))); + } + + @Test + public void shouldRejectMutatingInTextPContaining() { + assertThrows(IllegalArgumentException.class, () -> + TextP.containing(__.addV("x").values("name"))); + } + + // ===== MUTATION CONTEXT: should reject DropStep but allow other mutations ===== + + @Test + public void shouldRejectDropInPropertyTraversal() { + assertThrows(IllegalArgumentException.class, () -> + g.V().property(__.V().map(__.drop()).project("x").by("name"))); + } + + @Test + public void shouldRejectNestedDropInPropertyTraversal() { + assertThrows(IllegalArgumentException.class, () -> + g.V().property(__.V().union(__.drop(), __.constant("x")).project("k").by())); + } + + // ===== VALID TRAVERSALS: should pass without error ===== + + @Test + public void shouldAllowReadOnlyHasTraversal() { + // Should not throw + g.V().has("name", __.V().values("name")); + } + + @Test + public void shouldAllowNavigationInHasTraversal() { + // Should not throw + g.V().has("name", __.V().out("knows").values("name")); + } + + @Test + public void shouldAllowReadOnlyLookupTraversal() { + // Should not throw + g.V().V(__.out("knows").id()); + } + + @Test + public void shouldAllowReadOnlyStartStepV() { + // Should not throw + g.V(__.V().id()); + } + + @Test + public void shouldAllowReadOnlyPredicate() { + // Should not throw + P.eq(__.V().values("age")); + } + + @Test + public void shouldAllowAddVInMutationContext() { + // addV is allowed in property(traversal) — only DropStep is blocked + g.V().property(__.V().addV("temp").project("k").by("name")); + } + + @Test + public void shouldAllowReadOnlyPropertyTraversal() { + // Should not throw + g.V().property(__.V().project("name").by("name")); + } +} diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index e349b0f622..d460dcadeb 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -373,6 +373,22 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"g_injectX7X_anyXeqX7XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(7).Any(P.Eq(7))}}, {"g_injectXnull_nullX_anyXeqXnullXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { null, null }).Any(P.Eq(null))}}, {"g_injectX3_threeX_anyXeqX3XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { 3, "three" }).Any(P.Eq(3))}}, + {"g_V_hasXname_addVXxX_valuesXnameXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", __.AddV((string) "x").Values<object>("name"))}}, + {"g_V_hasXname_V_drop_constantXxXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", __.V().Drop().Constant<object>("x"))}}, + {"g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", __.V().Map<object>(__.AddV((string) "x")).Values<object>("name"))}}, + {"g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", P.Eq(__.AddV((string) "x").Values<object>("name")))}}, + {"g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", P.Within(__.AddV((string) "x").Values<object>("name")))}}, + {"g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age", P.Gt(__.AddV((string) "x").Values<object>("age")))}}, + {"g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Is(__.AddV((string) "x").Values<object>("age"))}}, + {"g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Is(P.Gt(__.AddV((string) "x").Values<object>("age")))}}, + {"g_V_VXaddVXxX_idX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().V(__.AddV((string) "x").Id())}}, + {"g_V_EXaddVXxX_idX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().E(__.AddV((string) "x").Id())}}, + {"g_VXaddVXxX_idX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(__.AddV((string) "x").Id())}}, + {"g_EXaddVXxX_idX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.E(__.AddV((string) "x").Id())}}, + {"g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Property((ITraversal) __.V().Map<object>(__.Drop()).Project<object>("x").By("name"))}}, + {"g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("name", __.V(p["vid1"]).Values<object>("name"))}}, + {"g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Has("age", P.Gt(__.V(p["vid1"]).Values<object>("age")))}}, + {"g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).V(__.Out("knows").Id()).Values<object>("name")}}, {"g_V_coinX1_0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(1.0)}}, {"g_V_coinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(1)}}, {"g_V_coinX0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(0.0)}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 1ff3d19a48..60808e97d4 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -343,6 +343,22 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "g_injectX7X_anyXeqX7XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(7).Any(gremlingo.P.Eq(7))}}, "g_injectXnull_nullX_anyXeqXnullXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{nil, nil}).Any(gremlingo.P.Eq(nil))}}, "g_injectX3_threeX_anyXeqX3XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{3, "three"}).Any(gremlingo.P.Eq(3))}}, + "g_V_hasXname_addVXxX_valuesXnameXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.T__.AddV("x").Values("name"))}}, + "g_V_hasXname_V_drop_constantXxXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.T__.V().Drop().Constant("x"))}}, + "g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.T__.V().Map(gremlingo.T__.AddV("x")).Values("name"))}}, + "g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.P.Eq(gremlingo.T__.AddV("x").Values("name")))}}, + "g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.P.Within(gremlingo.T__.AddV("x").Values("name")))}}, + "g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("age", gremlingo.P.Gt(gremlingo.T__.AddV("x").Values("age")))}}, + "g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Is(gremlingo.T__.AddV("x").Values("age"))}}, + "g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Values("age").Is(gremlingo.P.Gt(gremlingo.T__.AddV("x").Values("age")))}}, + "g_V_VXaddVXxX_idX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().V(gremlingo.T__.AddV("x").Id())}}, + "g_V_EXaddVXxX_idX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().E(gremlingo.T__.AddV("x").Id())}}, + "g_VXaddVXxX_idX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(gremlingo.T__.AddV("x").Id())}}, + "g_EXaddVXxX_idX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.E(gremlingo.T__.AddV("x").Id())}}, + "g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Property(gremlingo.T__.V().Map(gremlingo.T__.Drop()).Project("x").By("name"))}}, + "g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("name", gremlingo.T__.V(p["vid1"]).Values("name"))}}, + "g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Has("age", gremlingo.P.Gt(gremlingo.T__.V(p["vid1"]).Values("age")))}}, + "g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V(p["vid1"]).V(gremlingo.T__.Out("knows").Id()).Values("name")}}, "g_V_coinX1_0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(1.0)}}, "g_V_coinX1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(1)}}, "g_V_coinX0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(0.0)}}, diff --git a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js index 3dc2da180a..c2a1640ae4 100644 --- a/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js +++ b/gremlin-js/gremlin-javascript/test/cucumber/gremlin.js @@ -374,6 +374,22 @@ const gremlins = { g_injectX7X_anyXeqX7XX: [function({g}) { return g.inject(7).any(P.eq(7)) }], g_injectXnull_nullX_anyXeqXnullXX: [function({g}) { return g.inject([null, null]).any(P.eq(null)) }], g_injectX3_threeX_anyXeqX3XX: [function({g}) { return g.inject([3, "three"]).any(P.eq(3)) }], + g_V_hasXname_addVXxX_valuesXnameXX_rejected: [function({g}) { return g.V().has("name", __.addV("x").values("name")) }], + g_V_hasXname_V_drop_constantXxXX_rejected: [function({g}) { return g.V().has("name", __.V().drop().constant("x")) }], + g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected: [function({g}) { return g.V().has("name", __.V().map(__.addV("x")).values("name")) }], + g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected: [function({g}) { return g.V().has("name", P.eq(__.addV("x").values("name"))) }], + g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected: [function({g}) { return g.V().has("name", P.within(__.addV("x").values("name"))) }], + g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected: [function({g}) { return g.V().has("age", P.gt(__.addV("x").values("age"))) }], + g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected: [function({g}) { return g.V().values("age").is(__.addV("x").values("age")) }], + g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected: [function({g}) { return g.V().values("age").is(P.gt(__.addV("x").values("age"))) }], + g_V_VXaddVXxX_idX_rejected: [function({g}) { return g.V().V(__.addV("x").id()) }], + g_V_EXaddVXxX_idX_rejected: [function({g}) { return g.V().E(__.addV("x").id()) }], + g_VXaddVXxX_idX_rejected: [function({g}) { return g.V(__.addV("x").id()) }], + g_EXaddVXxX_idX_rejected: [function({g}) { return g.E(__.addV("x").id()) }], + g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected: [function({g}) { return g.V().property(__.V().map(__.drop()).project("x").by("name")) }], + g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification: [function({g, vid1}) { return g.V().has("name", __.V(vid1).values("name")) }], + g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification: [function({g, vid1}) { return g.V().has("age", P.gt(__.V(vid1).values("age"))) }], + g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification: [function({g, vid1}) { return g.V(vid1).V(__.out("knows").id()).values("name") }], g_V_coinX1_0X: [function({g}) { return g.V().coin(1.0) }], g_V_coinX1X: [function({g}) { return g.V().coin(1) }], g_V_coinX0X: [function({g}) { return g.V().coin(0.0) }], diff --git a/gremlin-python/src/main/python/tests/feature/gremlin.py b/gremlin-python/src/main/python/tests/feature/gremlin.py index c5b2e09b1e..aa4358cfe7 100644 --- a/gremlin-python/src/main/python/tests/feature/gremlin.py +++ b/gremlin-python/src/main/python/tests/feature/gremlin.py @@ -348,6 +348,22 @@ world.gremlins = { 'g_injectX7X_anyXeqX7XX': [(lambda g:g.inject(7).any_(P.eq(7)))], 'g_injectXnull_nullX_anyXeqXnullXX': [(lambda g:g.inject([None, None]).any_(P.eq(None)))], 'g_injectX3_threeX_anyXeqX3XX': [(lambda g:g.inject([3, 'three']).any_(P.eq(3)))], + 'g_V_hasXname_addVXxX_valuesXnameXX_rejected': [(lambda g:g.V().has('name', __.add_v('x').values('name')))], + 'g_V_hasXname_V_drop_constantXxXX_rejected': [(lambda g:g.V().has('name', __.V().drop().constant('x')))], + 'g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected': [(lambda g:g.V().has('name', __.V().map(__.add_v('x')).values('name')))], + 'g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected': [(lambda g:g.V().has('name', P.eq(__.add_v('x').values('name'))))], + 'g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected': [(lambda g:g.V().has('name', P.within(__.add_v('x').values('name'))))], + 'g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected': [(lambda g:g.V().has('age', P.gt(__.add_v('x').values('age'))))], + 'g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected': [(lambda g:g.V().values('age').is_(__.add_v('x').values('age')))], + 'g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected': [(lambda g:g.V().values('age').is_(P.gt(__.add_v('x').values('age'))))], + 'g_V_VXaddVXxX_idX_rejected': [(lambda g:g.V().V(__.add_v('x').id_()))], + 'g_V_EXaddVXxX_idX_rejected': [(lambda g:g.V().E(__.add_v('x').id_()))], + 'g_VXaddVXxX_idX_rejected': [(lambda g:g.V(__.add_v('x').id_()))], + 'g_EXaddVXxX_idX_rejected': [(lambda g:g.E(__.add_v('x').id_()))], + 'g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected': [(lambda g:g.V().property(__.V().map(__.drop()).project('x').by('name')))], + 'g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification': [(lambda g, vid1=None:g.V().has('name', __.V(vid1).values('name')))], + 'g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification': [(lambda g, vid1=None:g.V().has('age', P.gt(__.V(vid1).values('age'))))], + 'g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification': [(lambda g, vid1=None:g.V(vid1).V(__.out('knows').id_()).values('name'))], 'g_V_coinX1_0X': [(lambda g:g.V().coin(1.0))], 'g_V_coinX1X': [(lambda g:g.V().coin(1))], 'g_V_coinX0X': [(lambda g:g.V().coin(0.0))], diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json index b19cdfa20f..99148d3a53 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/language/translator/translations.json @@ -6295,6 +6295,278 @@ } ] }, + { + "scenario": "g_V_hasXname_addVXxX_valuesXnameXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "language": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "canonical": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "anonymized": "g.V().has(string0, __.addV(string1).values(string0))", + "dotnet": "g.V().Has(\"name\", __.AddV((string) \"x\").Values<object>(\"name\"))", + "go": "g.V().Has(\"name\", gremlingo.T__.AddV(\"x\").Values(\"name\"))", + "groovy": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "java": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "javascript": "g.V().has(\"name\", __.addV(\"x\").values(\"name\"))", + "python": "g.V().has('name', __.add_v('x').values('name'))" + } + ] + }, + { + "scenario": "g_V_hasXname_V_drop_constantXxXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "language": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "canonical": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "anonymized": "g.V().has(string0, __.V().drop().constant(string1))", + "dotnet": "g.V().Has(\"name\", __.V().Drop().Constant<object>(\"x\"))", + "go": "g.V().Has(\"name\", gremlingo.T__.V().Drop().Constant(\"x\"))", + "groovy": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "java": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "javascript": "g.V().has(\"name\", __.V().drop().constant(\"x\"))", + "python": "g.V().has('name', __.V().drop().constant('x'))" + } + ] + }, + { + "scenario": "g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "language": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "canonical": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "anonymized": "g.V().has(string0, __.V().map(__.addV(string1)).values(string0))", + "dotnet": "g.V().Has(\"name\", __.V().Map<object>(__.AddV((string) \"x\")).Values<object>(\"name\"))", + "go": "g.V().Has(\"name\", gremlingo.T__.V().Map(gremlingo.T__.AddV(\"x\")).Values(\"name\"))", + "groovy": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "java": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "javascript": "g.V().has(\"name\", __.V().map(__.addV(\"x\")).values(\"name\"))", + "python": "g.V().has('name', __.V().map(__.add_v('x')).values('name'))" + } + ] + }, + { + "scenario": "g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "language": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "canonical": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "anonymized": "g.V().has(string0, P.eq(__.addV(string1).values(string0)))", + "dotnet": "g.V().Has(\"name\", P.Eq(__.AddV((string) \"x\").Values<object>(\"name\")))", + "go": "g.V().Has(\"name\", gremlingo.P.Eq(gremlingo.T__.AddV(\"x\").Values(\"name\")))", + "groovy": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "java": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "javascript": "g.V().has(\"name\", P.eq(__.addV(\"x\").values(\"name\")))", + "python": "g.V().has('name', P.eq(__.add_v('x').values('name')))" + } + ] + }, + { + "scenario": "g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "language": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "canonical": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "anonymized": "g.V().has(string0, P.within(__.addV(string1).values(string0)))", + "dotnet": "g.V().Has(\"name\", P.Within(__.AddV((string) \"x\").Values<object>(\"name\")))", + "go": "g.V().Has(\"name\", gremlingo.P.Within(gremlingo.T__.AddV(\"x\").Values(\"name\")))", + "groovy": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "java": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "javascript": "g.V().has(\"name\", P.within(__.addV(\"x\").values(\"name\")))", + "python": "g.V().has('name', P.within(__.add_v('x').values('name')))" + } + ] + }, + { + "scenario": "g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected", + "traversals": [ + { + "original": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "language": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "canonical": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "anonymized": "g.V().has(string0, P.gt(__.addV(string1).values(string0)))", + "dotnet": "g.V().Has(\"age\", P.Gt(__.AddV((string) \"x\").Values<object>(\"age\")))", + "go": "g.V().Has(\"age\", gremlingo.P.Gt(gremlingo.T__.AddV(\"x\").Values(\"age\")))", + "groovy": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "java": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "javascript": "g.V().has(\"age\", P.gt(__.addV(\"x\").values(\"age\")))", + "python": "g.V().has('age', P.gt(__.add_v('x').values('age')))" + } + ] + }, + { + "scenario": "g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected", + "traversals": [ + { + "original": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "language": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "canonical": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "anonymized": "g.V().values(string0).is(__.addV(string1).values(string0))", + "dotnet": "g.V().Values<object>(\"age\").Is(__.AddV((string) \"x\").Values<object>(\"age\"))", + "go": "g.V().Values(\"age\").Is(gremlingo.T__.AddV(\"x\").Values(\"age\"))", + "groovy": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "java": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "javascript": "g.V().values(\"age\").is(__.addV(\"x\").values(\"age\"))", + "python": "g.V().values('age').is_(__.add_v('x').values('age'))" + } + ] + }, + { + "scenario": "g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected", + "traversals": [ + { + "original": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "language": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "canonical": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "anonymized": "g.V().values(string0).is(P.gt(__.addV(string1).values(string0)))", + "dotnet": "g.V().Values<object>(\"age\").Is(P.Gt(__.AddV((string) \"x\").Values<object>(\"age\")))", + "go": "g.V().Values(\"age\").Is(gremlingo.P.Gt(gremlingo.T__.AddV(\"x\").Values(\"age\")))", + "groovy": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "java": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "javascript": "g.V().values(\"age\").is(P.gt(__.addV(\"x\").values(\"age\")))", + "python": "g.V().values('age').is_(P.gt(__.add_v('x').values('age')))" + } + ] + }, + { + "scenario": "g_V_VXaddVXxX_idX_rejected", + "traversals": [ + { + "original": "g.V().V(__.addV(\"x\").id())", + "language": "g.V().V(__.addV(\"x\").id())", + "canonical": "g.V().V(__.addV(\"x\").id())", + "anonymized": "g.V().V(__.addV(string0).id())", + "dotnet": "g.V().V(__.AddV((string) \"x\").Id())", + "go": "g.V().V(gremlingo.T__.AddV(\"x\").Id())", + "groovy": "g.V().V(__.addV(\"x\").id())", + "java": "g.V().V(__.addV(\"x\").id())", + "javascript": "g.V().V(__.addV(\"x\").id())", + "python": "g.V().V(__.add_v('x').id_())" + } + ] + }, + { + "scenario": "g_V_EXaddVXxX_idX_rejected", + "traversals": [ + { + "original": "g.V().E(__.addV(\"x\").id())", + "language": "g.V().E(__.addV(\"x\").id())", + "canonical": "g.V().E(__.addV(\"x\").id())", + "anonymized": "g.V().E(__.addV(string0).id())", + "dotnet": "g.V().E(__.AddV((string) \"x\").Id())", + "go": "g.V().E(gremlingo.T__.AddV(\"x\").Id())", + "groovy": "g.V().E(__.addV(\"x\").id())", + "java": "g.V().E(__.addV(\"x\").id())", + "javascript": "g.V().E(__.addV(\"x\").id())", + "python": "g.V().E(__.add_v('x').id_())" + } + ] + }, + { + "scenario": "g_VXaddVXxX_idX_rejected", + "traversals": [ + { + "original": "g.V(__.addV(\"x\").id())", + "language": "g.V(__.addV(\"x\").id())", + "canonical": "g.V(__.addV(\"x\").id())", + "anonymized": "g.V(__.addV(string0).id())", + "dotnet": "g.V(__.AddV((string) \"x\").Id())", + "go": "g.V(gremlingo.T__.AddV(\"x\").Id())", + "groovy": "g.V(__.addV(\"x\").id())", + "java": "g.V(__.addV(\"x\").id())", + "javascript": "g.V(__.addV(\"x\").id())", + "python": "g.V(__.add_v('x').id_())" + } + ] + }, + { + "scenario": "g_EXaddVXxX_idX_rejected", + "traversals": [ + { + "original": "g.E(__.addV(\"x\").id())", + "language": "g.E(__.addV(\"x\").id())", + "canonical": "g.E(__.addV(\"x\").id())", + "anonymized": "g.E(__.addV(string0).id())", + "dotnet": "g.E(__.AddV((string) \"x\").Id())", + "go": "g.E(gremlingo.T__.AddV(\"x\").Id())", + "groovy": "g.E(__.addV(\"x\").id())", + "java": "g.E(__.addV(\"x\").id())", + "javascript": "g.E(__.addV(\"x\").id())", + "python": "g.E(__.add_v('x').id_())" + } + ] + }, + { + "scenario": "g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected", + "traversals": [ + { + "original": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "language": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "canonical": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "anonymized": "g.V().property(__.V().map(__.drop()).project(string0).by(string1))", + "dotnet": "g.V().Property((ITraversal) __.V().Map<object>(__.Drop()).Project<object>(\"x\").By(\"name\"))", + "go": "g.V().Property(gremlingo.T__.V().Map(gremlingo.T__.Drop()).Project(\"x\").By(\"name\"))", + "groovy": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "java": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "javascript": "g.V().property(__.V().map(__.drop()).project(\"x\").by(\"name\"))", + "python": "g.V().property(__.V().map(__.drop()).project('x').by('name'))" + } + ] + }, + { + "scenario": "g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification", + "traversals": [ + { + "original": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "language": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "canonical": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "anonymized": "g.V().has(string0, __.V(vid1).values(string0))", + "dotnet": "g.V().Has(\"name\", __.V(vid1).Values<object>(\"name\"))", + "go": "g.V().Has(\"name\", gremlingo.T__.V(vid1).Values(\"name\"))", + "groovy": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "java": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "javascript": "g.V().has(\"name\", __.V(vid1).values(\"name\"))", + "python": "g.V().has('name', __.V(vid1).values('name'))" + } + ] + }, + { + "scenario": "g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification", + "traversals": [ + { + "original": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "language": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "canonical": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "anonymized": "g.V().has(string0, P.gt(__.V(vid1).values(string0)))", + "dotnet": "g.V().Has(\"age\", P.Gt(__.V(vid1).Values<object>(\"age\")))", + "go": "g.V().Has(\"age\", gremlingo.P.Gt(gremlingo.T__.V(vid1).Values(\"age\")))", + "groovy": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "java": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "javascript": "g.V().has(\"age\", P.gt(__.V(vid1).values(\"age\")))", + "python": "g.V().has('age', P.gt(__.V(vid1).values('age')))" + } + ] + }, + { + "scenario": "g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification", + "traversals": [ + { + "original": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "language": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "canonical": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "anonymized": "g.V(vid1).V(__.out(string0).id()).values(string1)", + "dotnet": "g.V(vid1).V(__.Out(\"knows\").Id()).Values<object>(\"name\")", + "go": "g.V(vid1).V(gremlingo.T__.Out(\"knows\").Id()).Values(\"name\")", + "groovy": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "java": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "javascript": "g.V(vid1).V(__.out(\"knows\").id()).values(\"name\")", + "python": "g.V(vid1).V(__.out('knows').id_()).values('name')" + } + ] + }, { "scenario": "g_V_coinX1_0X", "traversals": [ diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/ChildTraversalVerification.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/ChildTraversalVerification.feature new file mode 100644 index 0000000000..1bd57ef418 --- /dev/null +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/filter/ChildTraversalVerification.feature @@ -0,0 +1,202 @@ +# 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. + +@StepClassFilter @StepHas +Feature: Child Traversal Verification - mutating steps blocked in filter/lookup contexts + + # ===== FILTER CONTEXT: has() with mutating child traversals ===== + # All error scenarios use addV()/drop() which ComputerVerificationStrategy also rejects on OLAP + # with a different message, so we exclude them from GraphComputer runs. + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_addVXxX_valuesXnameXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("name", __.addV("x").values("name")) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_V_drop_constantXxXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("name", __.V().drop().constant("x")) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_V_mapXaddVXxXX_valuesXnameXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("name", __.V().map(__.addV("x")).values("name")) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_eqXaddVXxX_valuesXnameXXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("name", P.eq(__.addV("x").values("name"))) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_withinXaddVXxX_valuesXnameXXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("name", P.within(__.addV("x").values("name"))) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXage_gtXaddVXxX_valuesXageXXX_rejected + Given the modern graph + And the traversal of + """ + g.V().has("age", P.gt(__.addV("x").values("age"))) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + # ===== FILTER CONTEXT: is() with mutating child traversals ===== + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_valuesXageX_isXaddVXxX_valuesXageXX_rejected + Given the modern graph + And the traversal of + """ + g.V().values("age").is(__.addV("x").values("age")) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_valuesXageX_isXgtXaddVXxX_valuesXageXXX_rejected + Given the modern graph + And the traversal of + """ + g.V().values("age").is(P.gt(__.addV("x").values("age"))) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + # ===== LOOKUP CONTEXT: V()/E() with mutating child traversals ===== + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_VXaddVXxX_idX_rejected + Given the modern graph + And the traversal of + """ + g.V().V(__.addV("x").id()) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidENotSupported + Scenario: g_V_EXaddVXxX_idX_rejected + Given the modern graph + And the traversal of + """ + g.V().E(__.addV("x").id()) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_VXaddVXxX_idX_rejected + Given the modern graph + And the traversal of + """ + g.V(__.addV("x").id()) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + @GraphComputerVerificationMidVNotSupported + Scenario: g_EXaddVXxX_idX_rejected + Given the modern graph + And the traversal of + """ + g.E(__.addV("x").id()) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "mutating step" + + # ===== MUTATION CONTEXT: property(traversal) blocks DropStep but allows other mutations ===== + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_propertyXV_mapXdropX_projectXxX_byXnameXX_rejected + Given the modern graph + And the traversal of + """ + g.V().property(__.V().map(__.drop()).project("x").by("name")) + """ + When iterated to list + Then the traversal will raise an error with message containing text of "DropStep" + + # ===== VALID TRAVERSALS: should NOT be rejected ===== + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXname_VXvid1X_valuesXnameXX_passes_verification + Given the modern graph + And using the parameter vid1 defined as "v[marko].id" + And the traversal of + """ + g.V().has("name", __.V(vid1).values("name")) + """ + When iterated to list + Then the result should be unordered + | result | + | v[marko] | + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_hasXage_gtXVXvid1X_valuesXageXXX_passes_verification + Given the modern graph + And using the parameter vid1 defined as "v[marko].id" + And the traversal of + """ + g.V().has("age", P.gt(__.V(vid1).values("age"))) + """ + When iterated to list + Then the result should be unordered + | result | + | v[josh] | + | v[peter] | + + @GraphComputerVerificationMidVNotSupported + Scenario: g_V_VXoutXknowsX_idX_valuesXnameX_passes_verification + Given the modern graph + And using the parameter vid1 defined as "v[marko].id" + And the traversal of + """ + g.V(vid1).V(__.out("knows").id()).values("name") + """ + When iterated to list + Then the result should be unordered + | result | + | vadas | + | josh |
