You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by sp...@apache.org on 2021/12/03 22:03:48 UTC
[tinkerpop] 01/02: TINKERPOP-2635 Add fail() step
This is an automated email from the ASF dual-hosted git repository.
spmallette pushed a commit to branch TINKERPOP-2635
in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 2948ae6c024bedaa932f5ad974bfa2abf9e47f61
Author: Stephen Mallette <st...@amazon.com>
AuthorDate: Tue Nov 30 13:57:30 2021 -0500
TINKERPOP-2635 Add fail() step
---
CHANGELOG.asciidoc | 2 +
docs/src/dev/developer/for-committers.asciidoc | 1 +
docs/src/reference/the-traversal.asciidoc | 37 ++++++
docs/src/upgrade/release-3.6.x.asciidoc | 23 +++-
.../tinkerpop/gremlin/console/Console.groovy | 124 +++++++++++++++------
.../language/grammar/GremlinBaseVisitor.java | 13 +++
.../language/grammar/TraversalMethodVisitor.java | 17 +++
.../gremlin/process/traversal/Failure.java | 101 +++++++++++++++++
.../gremlin/process/traversal/Traverser.java | 9 ++
.../traversal/dsl/graph/GraphTraversal.java | 54 +++++++--
.../gremlin/process/traversal/dsl/graph/__.java | 14 +++
.../traversal/step/sideEffect/FailStep.java | 87 +++++++++++++++
.../traverser/B_LP_NL_O_S_SE_SL_Traverser.java | 5 +
.../traverser/B_LP_O_S_SE_SL_Traverser.java | 1 -
.../traverser/B_NL_O_S_SE_SL_Traverser.java | 1 -
.../traversal/traverser/B_O_S_SE_SL_Traverser.java | 7 ++
.../traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java | 6 +
.../traverser/LP_NL_O_OB_S_SE_SL_Traverser.java | 6 +
.../traverser/LP_O_OB_S_SE_SL_Traverser.java | 1 -
.../traverser/NL_O_OB_S_SE_SL_Traverser.java | 6 +
.../traverser/O_OB_S_SE_SL_Traverser.java | 9 +-
.../Process/Traversal/GraphTraversal.cs | 27 +++++
.../src/Gremlin.Net/Process/Traversal/__.cs | 16 +++
.../Gherkin/CommonSteps.cs | 67 ++++++++---
.../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 5 +
.../apache/tinkerpop/gremlin/driver/Tokens.java | 15 +++
.../gremlin/driver/message/ResponseStatusCode.java | 11 ++
.../ast/VarAsBindingASTTransformation.groovy | 3 +
.../lib/process/graph-traversal.js | 11 ++
.../test/cucumber/feature-steps.js | 14 ++-
.../gremlin-javascript/test/cucumber/gremlin.js | 3 +
gremlin-language/src/main/antlr4/Gremlin.g4 | 6 +
.../gremlin_python/process/graph_traversal.py | 14 +++
.../src/main/python/radish/feature_steps.py | 24 +++-
gremlin-python/src/main/python/radish/gremlin.py | 3 +
.../gremlin/server/handler/AbstractSession.java | 31 ++++--
.../gremlin/server/op/AbstractEvalOpProcessor.java | 25 +++--
.../gremlin/server/op/AbstractOpProcessor.java | 9 +-
.../server/op/session/SessionOpProcessor.java | 67 ++++++++---
.../server/op/traversal/TraversalOpProcessor.java | 47 +++++---
.../gremlin/server/GremlinServerIntegrateTest.java | 16 +++
gremlin-test/features/sideEffect/Fail.feature | 46 ++++++++
.../tinkerpop/gremlin/features/StepDefinition.java | 40 ++++++-
43 files changed, 895 insertions(+), 129 deletions(-)
diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index f610897..703ba95 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -25,6 +25,8 @@ limitations under the License.
* Changed TinkerGraph to allow identifiers to be heterogeneous when filtering.
* Prevented values of `T` to `property()` from being `null`.
+* Added `fail()` step.
+* Improved Gherkin test framework to allow for asserting traversal exceptions as a behavior.
* Fixed query indentation for profile metrics where indent levels were not being respected.
* `TraversalOpProcessor` no longer accepts a `String` representation of `Bytecode` for the "gremlin" argument which was left to support older versions of the drivers.
* Removed requirement that "ids" used to filter vertices and edges need to be all of a single type.
diff --git a/docs/src/dev/developer/for-committers.asciidoc b/docs/src/dev/developer/for-committers.asciidoc
index ccddf9c..4114259 100644
--- a/docs/src/dev/developer/for-committers.asciidoc
+++ b/docs/src/dev/developer/for-committers.asciidoc
@@ -448,6 +448,7 @@ The "Then" options handle the assertion of the result. There are several options
* "the result should have a count of _xxx_" - assumes a list value in the result and counts the number of values
in it
* "the result should be empty" - no results
+* "the traversal will raise an error" - an exception is thrown as a result of traversal iteration
* "the result should be ordered" - the exact results and should appear in the order presented
* "the result should be unordered" - the exact results but can appear any order
* "the result should be of" - results can be any of the specified values and in any order (use when guarantees
diff --git a/docs/src/reference/the-traversal.asciidoc b/docs/src/reference/the-traversal.asciidoc
index c888553..0e7b0ed 100644
--- a/docs/src/reference/the-traversal.asciidoc
+++ b/docs/src/reference/the-traversal.asciidoc
@@ -1228,6 +1228,43 @@ g.V().hasLabel('person').outE().identity().inV().count().is(gt(5)).explain()
For traversal profiling information, please see <<profile-step,`profile()`>>-step.
+[[fail-step]]
+=== Fail Step
+
+The `fail()`-step provides a way to force a traversal to immediately fail with an exception. This feature is often
+helpful during debugging purposes and for validating certain conditions prior to continuing with traversal execution.
+
+[source,text]
+----
+gremlin> g.V().has('person','name','peter').fold().
+......1> coalesce(unfold(),
+......2> fail('peter should exist')).
+......3> property('k',100)
+==>v[6]
+gremlin> g.V().has('person','name','stephen').fold().
+......1> coalesce(unfold(),
+......2> fail('stephen should exist')).
+......3> property('k',100)
+fail() Step Triggered
+===========================================================================================================================
+Message > stephen should exist
+Traverser> []
+ Bulk > 1
+Traversal> fail()
+Parent > CoalesceStep [V().has("person","name","stephen").fold().coalesce(__.unfold(),__.fail()).property("k",(int) 100)]
+Metadata > {}
+===========================================================================================================================
+----
+
+The code example above exemplifies the latter use case where there is essentially an assertion that there is a vertex
+with a particular "name" value prior to updating the property "k" and explicitly failing when that vertex is not found.
+
+*Additional References*
+
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#fail--++[`fail()`],
+link:++https://tinkerpop.apache.org/javadocs/x.y.z/core/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/GraphTraversal.html#fail-java.lang.String-++[`fail(String)`]
+
+
[[fold-step]]
=== Fold Step
diff --git a/docs/src/upgrade/release-3.6.x.asciidoc b/docs/src/upgrade/release-3.6.x.asciidoc
index 0f9a7b3..b4ae8d2 100644
--- a/docs/src/upgrade/release-3.6.x.asciidoc
+++ b/docs/src/upgrade/release-3.6.x.asciidoc
@@ -47,7 +47,28 @@ It is worth noting that `gremlin-groovy` utilized the DSL annotations to constru
link:https://tinkerpop.apache.org/docs/3.6.0/reference/#credentials-dsl[Credentials DSL] so the `gremlin-annotations`
package is now explicitly associated to `gremlin-groovy` but as an `<optional>` dependency.
-See:link:https://issues.apache.org/jira/browse/TINKERPOP-2411[TINKERPOP-2411]
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2411[TINKERPOP-2411]
+
+==== fail() Step
+
+The new `fail()` step provides a way to immediately terminate a traversal with a runtime exception. In the Gremlin
+Console, the exception will be rendered as follows which helps provide some context to the failure:
+
+[source,text]
+----
+gremlin> g.V().fail("nope!")
+fail() Step Triggered
+=====================
+Message > nope!
+Traverser> v[1]
+ Bulk > 1
+Traversal> V().fail()
+Metadata > {}
+=====================
+----
+
+See: link:https://issues.apache.org/jira/browse/TINKERPOP-2635[TINKERPOP-2635],
+link:https://tinkerpop.apache.org/docs/3.6.0/reference/#fail-step[Reference Documentation]
==== Null for T
diff --git a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
index c47bbf3..bf5fe0c 100644
--- a/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
+++ b/gremlin-console/src/main/groovy/org/apache/tinkerpop/gremlin/console/Console.groovy
@@ -36,6 +36,13 @@ import org.apache.tinkerpop.gremlin.jsr223.CoreGremlinPlugin
import org.apache.tinkerpop.gremlin.jsr223.GremlinPlugin
import org.apache.tinkerpop.gremlin.jsr223.ImportCustomizer
import org.apache.tinkerpop.gremlin.jsr223.console.RemoteException
+import org.apache.tinkerpop.gremlin.process.traversal.Failure
+import org.apache.tinkerpop.gremlin.process.traversal.Step
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep
+import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.B_LP_NL_O_P_S_SE_SL_Traverser
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement
import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalExplanation
import org.apache.tinkerpop.gremlin.structure.Edge
import org.apache.tinkerpop.gremlin.structure.T
@@ -346,47 +353,54 @@ class Console {
if (err instanceof Throwable) {
try {
final Throwable e = (Throwable) err
- String message = e.getMessage()
- if (null != message) {
- message = message.replace("startup failed:", "")
- io.err.println(Colorizer.render(Preferences.errorColor, message.trim()))
+
+ // special rendering for Failure
+ if ((err instanceof Failure)) {
+ def fail = (Failure) err
+ io.err.println(Colorizer.render(Preferences.errorColor, fail.format()))
} else {
- io.err.println(Colorizer.render(Preferences.errorColor,e))
- }
+ String message = e.getMessage()
+ if (null != message) {
+ message = message.replace("startup failed:", "")
+ io.err.println(Colorizer.render(Preferences.errorColor, message.trim()))
+ } else {
+ io.err.println(Colorizer.render(Preferences.errorColor, e))
+ }
- // provide a hint in the case of a stackoverflow as it can be common when running large Gremlin
- // scripts and it isn't immediately apparent what the error might mean in this context especially
- // if the user isn't familiar with the JVM. it really can only be a hint since we can't be completely
- // sure it arose as a result of a long Gremlin traversal.
- if (err instanceof StackOverflowError) {
- io.err.println(Colorizer.render(Preferences.errorColor,
- "A StackOverflowError can indicate that the Gremlin traversal being executed is too long. If " +
- "you have a single Gremlin statement that is \"long\", you may break it up into " +
- "multiple separate commands, re-write the traversal to operate on a stream of " +
- "input via inject() rather than literals, or attempt to increase the -Xss setting" +
- "of the Gremlin Console by modifying gremlin.sh."));
- }
+ // provide a hint in the case of a stackoverflow as it can be common when running large Gremlin
+ // scripts and it isn't immediately apparent what the error might mean in this context especially
+ // if the user isn't familiar with the JVM. it really can only be a hint since we can't be completely
+ // sure it arose as a result of a long Gremlin traversal.
+ if (err instanceof StackOverflowError) {
+ io.err.println(Colorizer.render(Preferences.errorColor,
+ "A StackOverflowError can indicate that the Gremlin traversal being executed is too long. If " +
+ "you have a single Gremlin statement that is \"long\", you may break it up into " +
+ "multiple separate commands, re-write the traversal to operate on a stream of " +
+ "input via inject() rather than literals, or attempt to increase the -Xss setting" +
+ "of the Gremlin Console by modifying gremlin.sh."));
+ }
- if (interactive) {
- io.err.println(Colorizer.render(Preferences.infoColor,"Type ':help' or ':h' for help."))
- io.err.print(Colorizer.render(Preferences.errorColor, "Display stack trace? [yN]"))
- io.err.flush()
- String line = new BufferedReader(io.in).readLine()
- if (null == line)
- line = ""
- io.err.print(line.trim())
- io.err.println()
- if (line.trim().equals("y") || line.trim().equals("Y")) {
- if (err instanceof RemoteException && err.remoteStackTrace.isPresent()) {
- io.err.print(err.remoteStackTrace.get())
- io.err.flush()
- } else {
- e.printStackTrace(io.err)
+ if (interactive) {
+ io.err.println(Colorizer.render(Preferences.infoColor, "Type ':help' or ':h' for help."))
+ io.err.print(Colorizer.render(Preferences.errorColor, "Display stack trace? [yN]"))
+ io.err.flush()
+ String line = new BufferedReader(io.in).readLine()
+ if (null == line)
+ line = ""
+ io.err.print(line.trim())
+ io.err.println()
+ if (line.trim().equals("y") || line.trim().equals("Y")) {
+ if (err instanceof RemoteException && err.remoteStackTrace.isPresent()) {
+ io.err.print(err.remoteStackTrace.get())
+ io.err.flush()
+ } else {
+ e.printStackTrace(io.err)
+ }
}
+ } else {
+ e.printStackTrace(io.err)
+ System.exit(1)
}
- } else {
- e.printStackTrace(io.err)
- System.exit(1)
}
} catch (Exception ignored) {
io.err.println(Colorizer.render(Preferences.errorColor, "An undefined error has occurred: " + err))
@@ -402,6 +416,44 @@ class Console {
return null
}
+ private def writeTraverserToErrorLines(Traverser t, List errorLines) {
+ // every traverser has an object so toString() that. pad with spaces to cover "side-effects" width
+ errorLines << "Traverser> $t"
+
+ def optGenerator = t.asAdmin().generator
+ if (optGenerator.isPresent()) {
+ def width = "Traverser".length()
+ def generator = optGenerator.get()
+ if (generator.providedRequirements.contains(TraverserRequirement.BULK)) {
+ errorLines << " Bulk".padRight(width) + "> " + t.bulk()
+ }
+
+ if (generator.providedRequirements.contains(TraverserRequirement.SACK)) {
+ errorLines << " Sack".padRight(width) + "> " + t.sack()
+ }
+
+ if (generator.providedRequirements.contains(TraverserRequirement.PATH)) {
+ errorLines << " Path".padRight(width) + "> " + t.path()
+ }
+
+ if (generator.providedRequirements.contains(TraverserRequirement.SINGLE_LOOP) ||
+ generator.providedRequirements.contains(TraverserRequirement.NESTED_LOOP)) {
+ // flatten loops/names if present
+ def loopNames = t.asAdmin().loopNames
+ def loopsLine = loopNames.isEmpty() ? t.loops() : loopNames.collect { [(it): t.loops(it)]}
+ errorLines << " Loops".padRight(width) + "> " + loopsLine
+ }
+
+ if (generator.providedRequirements.contains(TraverserRequirement.SIDE_EFFECTS)) {
+ // convert side-effects to a map
+ def sideEffects = t.asAdmin().sideEffects
+ def keys = sideEffects.keys()
+ errorLines << " S/E".padRight(width) + "> " + keys.collectEntries { [(it): sideEffects.get(it)]}
+ }
+
+ }
+ }
+
private static String buildResultPrompt() {
final String groovyshellProperty = System.getProperty("gremlin.prompt")
if (groovyshellProperty != null)
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
index 95f2874..faef075 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/GremlinBaseVisitor.java
@@ -348,6 +348,19 @@ public class GremlinBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@inheritDoc}
*/
@Override public T visitTraversalMethod_emit_Traversal(final GremlinParser.TraversalMethod_emit_TraversalContext ctx) { notImplemented(ctx); return null; }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_fail_Empty(final GremlinParser.TraversalMethod_fail_EmptyContext ctx) { notImplemented(ctx); return null; }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public T visitTraversalMethod_fail_String(final GremlinParser.TraversalMethod_fail_StringContext ctx) { notImplemented(ctx); return null; }
+
/**
* {@inheritDoc}
*/
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalMethodVisitor.java
index 67258ce..7682232 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
@@ -29,6 +29,7 @@ import org.apache.tinkerpop.gremlin.structure.Direction;
import org.apache.tinkerpop.gremlin.structure.T;
import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import java.util.Map;
import java.util.function.BiFunction;
import static org.apache.tinkerpop.gremlin.process.traversal.SackFunctions.Barrier.normSack;
@@ -453,6 +454,22 @@ public class TraversalMethodVisitor extends TraversalRootVisitor<GraphTraversal>
* {@inheritDoc}
*/
@Override
+ public Traversal visitTraversalMethod_fail_Empty(final GremlinParser.TraversalMethod_fail_EmptyContext ctx) {
+ return this.graphTraversal.fail();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Traversal visitTraversalMethod_fail_String(final GremlinParser.TraversalMethod_fail_StringContext ctx) {
+ return this.graphTraversal.fail(GenericLiteralVisitor.getStringLiteral(ctx.stringLiteral()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public GraphTraversal visitTraversalMethod_filter_Predicate(final GremlinParser.TraversalMethod_filter_PredicateContext ctx) {
return graphTraversal.filter(TraversalPredicateVisitor.getInstance().visitTraversalPredicate(ctx.traversalPredicate()));
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java
new file mode 100644
index 0000000..8e11c87
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Failure.java
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal;
+
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.FailStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep;
+import org.apache.tinkerpop.gremlin.process.traversal.translator.GroovyTranslator;
+import org.apache.tinkerpop.gremlin.process.traversal.traverser.TraverserRequirement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public interface Failure {
+
+ static Translator.ScriptTranslator TRANSLATOR = GroovyTranslator.of("");
+
+ String getMessage();
+
+ Map<String,Object> getMetadata();
+
+ Traverser.Admin getTraverser();
+
+ Traversal.Admin getTraversal();
+
+ /**
+ * Gets the {@code Failure} as a formatted string representation.
+ */
+ public default String format() {
+ final List<String> lines = new ArrayList<>();
+ final Step parentStep = (Step) getTraversal().getParent();
+
+ lines.add(String.format("Message > %s", getMessage()));
+ lines.add(String.format("Traverser> %s", getTraverser().toString()));
+
+ final TraverserGenerator generator = getTraversal().getTraverserGenerator();
+ final Traverser.Admin traverser = getTraverser();
+ if (generator.getProvidedRequirements().contains(TraverserRequirement.BULK)) {
+ lines.add(String.format(" Bulk > %s", traverser.bulk()));
+ }
+ if (generator.getProvidedRequirements().contains(TraverserRequirement.SACK)) {
+ lines.add(String.format(" Sack > %s", traverser.sack()));
+ }
+ if (generator.getProvidedRequirements().contains(TraverserRequirement.PATH)) {
+ lines.add(String.format(" Path > %s", traverser.path()));
+ }
+ if (generator.getProvidedRequirements().contains(TraverserRequirement.SINGLE_LOOP) ||
+ generator.getProvidedRequirements().contains(TraverserRequirement.NESTED_LOOP) ) {
+ final Set<String> loopNames = traverser.getLoopNames();
+ final String loopsLine = loopNames.isEmpty() ?
+ String.valueOf(traverser.asAdmin().loops()) :
+ loopNames.stream().collect(Collectors.toMap(loopName -> loopName, traverser::loops)).toString();
+ lines.add(String.format(" Loops > %s", loopsLine));
+ }
+ if (generator.getProvidedRequirements().contains(TraverserRequirement.SIDE_EFFECTS)) {
+ final TraversalSideEffects tse = traverser.getSideEffects();
+ final Set<String> keys = tse.keys();
+ lines.add(String.format(" S/E > %s", keys.stream().collect(Collectors.toMap(k -> k, tse::get))));
+ }
+
+ // removes the starting period so that "__.out()" simply presents as "out()"
+ lines.add(String.format("Traversal> %s", TRANSLATOR.translate(getTraversal()).getScript().substring(1)));
+
+ // not sure there is a situation where fail() would be used where it was not wrapped in a parent,
+ // but on the odd case that it is it can be handled
+ if (parentStep != EmptyStep.instance()) {
+ lines.add(String.format("Parent > %s [%s]",
+ parentStep.getClass().getSimpleName(), TRANSLATOR.translate(parentStep.getTraversal()).getScript().substring(1)));
+ }
+
+ lines.add(String.format("Metadata > %s", getMetadata()));
+
+ final int longestLineLength = lines.stream().mapToInt(String::length).max().getAsInt();
+ final String separatorLine = String.join("", Collections.nCopies(longestLineLength, "="));
+ lines.add(0, separatorLine);
+ lines.add(0, "fail() Step Triggered");
+ lines.add(separatorLine);
+
+ return String.join(System.lineSeparator(), lines);
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
index fc58c62..19eb689 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Traverser.java
@@ -22,7 +22,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.LoopsStep;
import org.apache.tinkerpop.gremlin.structure.util.Attachable;
import java.io.Serializable;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
@@ -325,5 +327,12 @@ public interface Traverser<T> extends Serializable, Comparable<Traverser<T>>, Cl
* @return the set of tags associated with the traverser.
*/
public Set<String> getTags();
+
+ /**
+ * If this traverser supports loops then return the loop names if any.
+ */
+ public default Set<String> getLoopNames() {
+ return Collections.emptySet();
+ }
}
}
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 8d11664..891ead0 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
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PageRank
import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.PeerPressureVertexProgramStep;
import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ProgramVertexProgramStep;
import org.apache.tinkerpop.gremlin.process.computer.traversal.step.map.ShortestPathVertexProgramStep;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.P;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
@@ -126,6 +127,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.step.map.UnfoldStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.map.VertexStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AggregateGlobalStep;
+import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.FailStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupCountSideEffectStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.GroupSideEffectStep;
import org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.IdentityStep;
@@ -1254,6 +1256,18 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
}
/**
+ * Filter all traversers in the traversal. This step has narrow use cases and is primarily intended for use as a
+ * signal to remote servers that {@link #iterate()} was called. While it may be directly used, it is often a sign
+ * that a traversal should be re-written in another form.
+ *
+ * @return the updated traversal with respective {@link NoneStep}.
+ */
+ @Override
+ default GraphTraversal<S, E> none() {
+ return (GraphTraversal<S, E>) Traversal.super.none();
+ }
+
+ /**
* Ensures that at least one of the provided traversals yield a result.
*
* @param orTraversals filter traversals where at least one must be satisfied
@@ -2154,6 +2168,33 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
}
/**
+ * When triggered, immediately throws a {@code RuntimeException} which implements the {@link Failure} interface.
+ * The traversal will be terminated as a result.
+ *
+ * @return the traversal with an appended {@link FailStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#fail-step" target="_blank">Reference Documentation - Fail Step</a>
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, E> fail() {
+ this.asAdmin().getBytecode().addStep(Symbols.fail);
+ return this.asAdmin().addStep(new FailStep<>(this.asAdmin()));
+ }
+
+ /**
+ * When triggered, immediately throws a {@code RuntimeException} which implements the {@link Failure} interface.
+ * The traversal will be terminated as a result.
+ *
+ * @param message the error message to include in the exception
+ * @return the traversal with an appended {@link FailStep}.
+ * @see <a href="http://tinkerpop.apache.org/docs/${project.version}/reference/#fail-step" target="_blank">Reference Documentation - Fail Step</a>
+ * @since 3.6.0
+ */
+ public default GraphTraversal<S, E> fail(final String message) {
+ this.asAdmin().getBytecode().addStep(Symbols.fail, message);
+ return this.asAdmin().addStep(new FailStep<>(this.asAdmin(), message));
+ }
+
+ /**
* Aggregates the emanating paths into a {@link Tree} data structure.
*
* @param sideEffectKey the name of the side-effect key that will hold the tree
@@ -2222,18 +2263,6 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
}
/**
- * Filter all traversers in the traversal. This step has narrow use cases and is primarily intended for use as a
- * signal to remote servers that {@link #iterate()} was called. While it may be directly used, it is often a sign
- * that a traversal should be re-written in another form.
- *
- * @return the updated traversal with respective {@link NoneStep}.
- */
- @Override
- default GraphTraversal<S, E> none() {
- return (GraphTraversal<S, E>) Traversal.super.none();
- }
-
- /**
* Sets a {@link Property} value and related meta properties if supplied, if supported by the {@link Graph}
* and if the {@link Element} is a {@link VertexProperty}. This method is the long-hand version of
* {@link #property(Object, Object, Object...)} with the difference that the {@link VertexProperty.Cardinality}
@@ -3156,6 +3185,7 @@ public interface GraphTraversal<S, E> extends Traversal<S, E> {
@Deprecated
public static final String store = "store";
public static final String aggregate = "aggregate";
+ public static final String fail = "fail";
public static final String subgraph = "subgraph";
public static final String barrier = "barrier";
public static final String index = "index";
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
index aab83f4..08a4c07 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/dsl/graph/__.java
@@ -912,6 +912,20 @@ public class __ {
}
/**
+ * @see GraphTraversal#fail()
+ */
+ public static <A> GraphTraversal<A, A> fail() {
+ return __.<A>start().fail();
+ }
+
+ /**
+ * @see GraphTraversal#fail(String)
+ */
+ public static <A> GraphTraversal<A, A> fail(final String message) {
+ return __.<A>start().fail(message);
+ }
+
+ /**
* @see GraphTraversal#group(String)
*/
public static <A> GraphTraversal<A, A> group(final String sideEffectKey) {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
new file mode 100644
index 0000000..48741f8
--- /dev/null
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/sideEffect/FailStep.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sideEffect;
+
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
+import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Triggers an immediate failure of the traversal by throwing a {@code RuntimeException}. The exception thrown must
+ * implement the {@link Failure} interface.
+ */
+public class FailStep<S> extends SideEffectStep<S> {
+
+ protected String message;
+ protected Map<String,Object> metadata;
+
+ public FailStep(final Traversal.Admin traversal) {
+ this(traversal, "fail() step triggered");
+ }
+
+ public FailStep(final Traversal.Admin traversal, final String message) {
+ this(traversal, message, Collections.emptyMap());
+ }
+
+ public FailStep(final Traversal.Admin traversal, final String message, final Map<String,Object> metadata) {
+ super(traversal);
+ this.message = message;
+ this.metadata = metadata;
+ }
+
+ @Override
+ protected void sideEffect(final Traverser.Admin<S> traverser) {
+ throw new FailException(traversal, traverser, message, metadata);
+ }
+
+ /**
+ * Default {@link Failure} implementation that is thrown by {@link FailStep}.
+ */
+ public static class FailException extends RuntimeException implements Failure {
+ private final Map<String,Object> metadata;
+ private final Traversal.Admin traversal;
+ private final Traverser.Admin traverser;
+
+ public FailException(final Traversal.Admin traversal, final Traverser.Admin traverser,
+ final String message, final Map<String,Object> metadata) {
+ super(message);
+ this.metadata = metadata;
+ this.traversal = traversal;
+ this.traverser = traverser;
+ }
+
+ @Override
+ public Map<String, Object> getMetadata() {
+ return metadata;
+ }
+
+ @Override
+ public Traverser.Admin getTraverser() {
+ return traverser;
+ }
+
+ @Override
+ public Traversal.Admin getTraversal() {
+ return traversal;
+ }
+ }
+}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
index 59287ac..401bfd2 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_NL_O_S_SE_SL_Traverser.java
@@ -23,6 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
import java.util.Iterator;
+import java.util.Set;
import java.util.Stack;
public class B_LP_NL_O_S_SE_SL_Traverser<T> extends B_LP_O_S_SE_SL_Traverser<T> {
@@ -65,6 +66,10 @@ public class B_LP_NL_O_S_SE_SL_Traverser<T> extends B_LP_O_S_SE_SL_Traverser<T>
this.loopNames.put(loopName, lc);
}
}
+ @Override
+ public Set<String> getLoopNames() {
+ return loopNames.keySet();
+ }
@Override
public void incrLoops() {
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
index 3a2aa38..1687909 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_LP_O_S_SE_SL_Traverser.java
@@ -19,7 +19,6 @@
package org.apache.tinkerpop.gremlin.process.traversal.traverser;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
-import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.ImmutablePath;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
index 20ed1be..6e18d12 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_NL_O_S_SE_SL_Traverser.java
@@ -18,7 +18,6 @@
*/
package org.apache.tinkerpop.gremlin.process.traversal.traverser;
-import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
index 57fa47a..904d8c7 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/B_O_S_SE_SL_Traverser.java
@@ -22,7 +22,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import java.util.Collections;
import java.util.Objects;
+import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -72,6 +74,11 @@ public class B_O_S_SE_SL_Traverser<T> extends B_O_Traverser<T> {
}
@Override
+ public Set<String> getLoopNames() {
+ return Collections.singleton(loopName);
+ }
+
+ @Override
public void initialiseLoops(final String stepLabel , final String loopName){
this.loopName = loopName;
}
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
index 3cb1dd3..77157b8 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_P_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
import java.util.Iterator;
+import java.util.Set;
import java.util.Stack;
public class LP_NL_O_OB_P_S_SE_SL_Traverser<T> extends LP_O_OB_P_S_SE_SL_Traverser<T> {
@@ -130,6 +131,11 @@ public class LP_NL_O_OB_P_S_SE_SL_Traverser<T> extends LP_O_OB_P_S_SE_SL_Travers
super.merge(other);
}
+ @Override
+ public Set<String> getLoopNames() {
+ return loopNames.keySet();
+ }
+
/////////////////
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
index a9cd7fb..14c2fe3 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_NL_O_OB_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
import java.util.Iterator;
+import java.util.Set;
import java.util.Stack;
public class LP_NL_O_OB_S_SE_SL_Traverser<T> extends LP_O_OB_S_SE_SL_Traverser<T> {
@@ -58,6 +59,11 @@ public class LP_NL_O_OB_S_SE_SL_Traverser<T> extends LP_O_OB_S_SE_SL_Traverser<T
}
@Override
+ public Set<String> getLoopNames() {
+ return loopNames.keySet();
+ }
+
+ @Override
public void initialiseLoops(final String stepLabel, final String loopName) {
if (this.nestedLoops.empty() || !this.nestedLoops.peek().hasLabel(stepLabel)) {
final LabelledCounter lc = new LabelledCounter(stepLabel, (short) 0);
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
index f6bb9cc..97038fe 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/LP_O_OB_S_SE_SL_Traverser.java
@@ -20,7 +20,6 @@
package org.apache.tinkerpop.gremlin.process.traversal.traverser;
import org.apache.tinkerpop.gremlin.process.traversal.Path;
-import org.apache.tinkerpop.gremlin.process.traversal.Pop;
import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
import org.apache.tinkerpop.gremlin.process.traversal.step.util.ImmutablePath;
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
index 72f43fe..c30e893 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/NL_O_OB_S_SE_SL_Traverser.java
@@ -24,6 +24,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.LabelledCounter;
import java.util.Iterator;
+import java.util.Set;
import java.util.Stack;
public class NL_O_OB_S_SE_SL_Traverser<T> extends O_OB_S_SE_SL_Traverser<T> {
@@ -130,6 +131,11 @@ public class NL_O_OB_S_SE_SL_Traverser<T> extends O_OB_S_SE_SL_Traverser<T> {
super.merge(other);
}
+ @Override
+ public Set<String> getLoopNames() {
+ return loopNames.keySet();
+ }
+
/////////////////
@Override
diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
index 601bcda..73dc644 100644
--- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
+++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/traverser/O_OB_S_SE_SL_Traverser.java
@@ -23,7 +23,9 @@ import org.apache.tinkerpop.gremlin.process.traversal.Step;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSideEffects;
import org.apache.tinkerpop.gremlin.process.traversal.Traverser;
+import java.util.Collections;
import java.util.Objects;
+import java.util.Set;
/**
* @author Marko A. Rodriguez (http://markorodriguez.com)
@@ -78,13 +80,18 @@ public class O_OB_S_SE_SL_Traverser<T> extends O_Traverser<T> {
@Override
public int loops(final String loopName) {
- if (loopName == null || this.loopName != null && this.loopName.equals(loopName))
+ if (loopName == null || this.loopName.equals(loopName))
return this.loops;
else
throw new IllegalArgumentException("Loop name not defined: " + loopName);
}
@Override
+ public Set<String> getLoopNames() {
+ return Collections.singleton(loopName);
+ }
+
+ @Override
public void initialiseLoops(final String stepLabel , final String loopName){
this.loopName = loopName;
}
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
index 328e506..ebd1cdf 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/GraphTraversal.cs
@@ -543,6 +543,33 @@ namespace Gremlin.Net.Process.Traversal
}
/// <summary>
+ /// Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, E> Fail ()
+ {
+ Bytecode.AddStep("fail");
+ return Wrap<S, E>(this);
+ }
+
+ /// <summary>
+ /// Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, E> Fail (string msg)
+ {
+ Bytecode.AddStep("fail", msg);
+ return Wrap<S, E>(this);
+ }
+
+ /// <summary>
+ /// Adds the fail step to this <see cref="GraphTraversal{SType, EType}" />.
+ /// </summary>
+ public GraphTraversal<S, E> Fail (string msg, IDictionary<string,object> m)
+ {
+ Bytecode.AddStep("fail", msg, m);
+ return Wrap<S, E>(this);
+ }
+
+ /// <summary>
/// Adds the filter step to this <see cref="GraphTraversal{SType, EType}" />.
/// </summary>
public GraphTraversal<S, E> Filter (IPredicate predicate)
diff --git a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
index fa1c027..acad588 100644
--- a/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
+++ b/gremlin-dotnet/src/Gremlin.Net/Process/Traversal/__.cs
@@ -364,6 +364,22 @@ namespace Gremlin.Net.Process.Traversal
}
/// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the fail step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, object> Fail()
+ {
+ return new GraphTraversal<object, object>().Fail();
+ }
+
+ /// <summary>
+ /// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the fail step to that traversal.
+ /// </summary>
+ public static GraphTraversal<object, object> Fail(string msg)
+ {
+ return new GraphTraversal<object, object>().Fail(msg);
+ }
+
+ /// <summary>
/// Spawns a <see cref="GraphTraversal{SType, EType}" /> and adds the filter step to that traversal.
/// </summary>
public static GraphTraversal<object, object> Filter(IPredicate predicate)
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
index 11e71c8..be8973c 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -47,6 +47,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
private readonly IDictionary<string, object> _parameters = new Dictionary<string, object>();
private ITraversal _traversal;
private object[] _result;
+ private Exception _error = null;
private static readonly JsonSerializerOptions JsonDeserializingOptions = new JsonSerializerOptions
{PropertyNamingPolicy = JsonNamingPolicy.CamelCase};
@@ -157,11 +158,18 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
}
ITraversal t = _traversal;
var list = new List<object>();
- while (t.MoveNext())
+ try
{
- list.Add(t.Current);
+ while (t.MoveNext())
+ {
+ list.Add(t.Current);
+ }
+ _result = list.ToArray();
+ }
+ catch (Exception ex)
+ {
+ _error = ex;
}
- _result = list.ToArray();
}
[When("iterated next")]
@@ -171,26 +179,44 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{
throw new InvalidOperationException("Traversal should be set before iterating");
}
- _traversal.MoveNext();
- var result = _traversal.Current;
- switch (result)
+
+ try
{
- case null:
- _result = null;
- return;
- case object[] arrayResult:
- _result = arrayResult;
- return;
- case IEnumerable enumerableResult:
- _result = enumerableResult.Cast<object>().ToArray();
- return;
+ _traversal.MoveNext();
+ var result = _traversal.Current;
+ switch (result)
+ {
+ case null:
+ _result = null;
+ return;
+ case object[] arrayResult:
+ _result = arrayResult;
+ return;
+ case IEnumerable enumerableResult:
+ _result = enumerableResult.Cast<object>().ToArray();
+ return;
+ }
+ }
+ catch (Exception ex)
+ {
+ _error = ex;
}
- throw new InvalidCastException($"Can not convert instance of {result.GetType()} to object[]");
+ }
+
+ [Then("the traversal will raise an error")]
+ public void TraversalWillRaiseError()
+ {
+ Assert.NotNull(_error);
+
+ // consume the error now that it has been asserted
+ _error = null;
}
[Then("the result should be (\\w+)")]
public void AssertResult(string characterizedAs, DataTable table = null)
{
+ assertThatNoErrorWasThrown();
+
var ordered = characterizedAs == "ordered";
switch (characterizedAs)
{
@@ -230,12 +256,16 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
[Then("the result should have a count of (\\d+)")]
public void AssertCount(int count)
{
+ assertThatNoErrorWasThrown();
+
Assert.Equal(count, _result.Length);
}
[Then("the graph should return (\\d+) for count of (.+)")]
public void AssertTraversalCount(int expectedCount, string traversalText)
{
+ assertThatNoErrorWasThrown();
+
if (traversalText.StartsWith("\""))
{
traversalText = traversalText.Substring(1, traversalText.Length - 2);
@@ -258,6 +288,11 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
}
+ private void assertThatNoErrorWasThrown()
+ {
+ if (_error != null) throw _error;
+ }
+
private static object ToMap(string stringMap, string graphName)
{
var jsonMap = JsonSerializer.Deserialize<JsonElement>(stringMap, JsonDeserializingOptions);
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
index 40bae6a..8977404 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs
@@ -456,7 +456,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_name_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Max<object>()}},
{"g_V_age_fold_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Fold().Max<object>(Scope.Local)}},
{"g_V_aggregateXaX_byXageX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("age").Cap<object>("a").Max<object>(Scope.Local)}},
+ {"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("age").Cap<object>("a").Max<object>(Scope.Local)}},
{"g_V_aggregateXaX_byXageX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("age").Cap<object>("a").Unfold<object>().Max<object>()}},
+ {"g_withStrategiesXProductiveByStrategyX_V_aggregateXaX_byXageX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new ProductiveByStrategy(productiveKeys: new List<object> {})).V().Aggregate("a").By("age").Cap<object>("a").Unfold<object>().Max<object>()}},
{"g_V_aggregateXaX_byXfooX_capXaX_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("foo").Cap<object>("a").Max<object>(Scope.Local)}},
{"g_V_aggregateXaX_byXfooX_capXaX_unfold_max", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("a").By("foo").Cap<object>("a").Unfold<object>().Max<object>()}},
{"g_V_foo_fold_maxXlocalX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("foo").Fold().Max<object>(Scope.Local)}},
@@ -715,6 +717,9 @@ namespace Gremlin.Net.IntegrationTest.Gherkin
{"g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate(Scope.Local,"a").By(__.OutE("created").Count()).Out().Out().Aggregate(Scope.Local,"a").By(__.InE("created").Values<object>("weight").Sum<object>()).Cap<object>("a")}},
{"g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("x").By(__.Values<object>("age").Is(P.Gt(29))).Cap<object>("x")}},
{"g_V_aggregateXxX_byXout_order_byXnameXX_capXxX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Aggregate("x").By(__.Out().Order().By("name")).Cap<object>("x")}},
+ {"g_V_fail", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail()}},
+ {"g_V_failXmsgX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Fail("msg")}},
+ {"g_V_unionXout_failX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Union<object>(__.Out(),__.Fail())}},
{"g_V_group_byXnameX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("name")}},
{"g_V_group_byXageX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("age")}},
{"g_V_group_byXnameX_by", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Group<object,object>().By("name").By()}},
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
index c5936a2..ba9b6fd 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/Tokens.java
@@ -19,6 +19,7 @@
package org.apache.tinkerpop.gremlin.driver;
import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.structure.Graph;
@@ -96,8 +97,22 @@ public final class Tokens {
public static final String VAL_TRAVERSAL_SOURCE_ALIAS = "g";
+ /**
+ * The value of this key holds a string representation of the data held by a {@link Failure} as produced by
+ * {@link Failure#format()}.
+ */
+ public static final String STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE = "failStepMessage";
+
+ /**
+ * Refers to the hierarchy of exception names for a particular exception thrown on the server.
+ */
public static final String STATUS_ATTRIBUTE_EXCEPTIONS = "exceptions";
+
+ /**
+ * Refers to the stacktrace for an exception thrown on the server
+ */
public static final String STATUS_ATTRIBUTE_STACK_TRACE = "stackTrace";
+
/**
* A {@link ResultSet#statusAttributes()} key for user-facing warnings.
* <p>
diff --git a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
index 4f43f65..88fce82 100644
--- a/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
+++ b/gremlin-driver/src/main/java/org/apache/tinkerpop/gremlin/driver/message/ResponseStatusCode.java
@@ -18,6 +18,9 @@
*/
package org.apache.tinkerpop.gremlin.driver.message;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
@@ -114,6 +117,14 @@ public enum ResponseStatusCode {
SERVER_ERROR(500),
/**
+ * A server error that is produced when the {@link GraphTraversal#fail()} step is triggered. The returned exception
+ * will include information consistent with the {@link Failure} interface.
+ *
+ * @since 3.6.0
+ */
+ SERVER_ERROR_FAIL_STEP(595),
+
+ /**
* A server error that indicates that the client should retry the request. A graph will typically return this error
* when a transaction fails due to a locking exception or some other sort of concurrent modification. In other
* words, the request was likely valid but the state of the server at the particular time the request arrived
diff --git a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
index 5cee02b..1da973d 100644
--- a/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
+++ b/gremlin-groovy/src/main/groovy/org/apache/tinkerpop/gremlin/groovy/jsr223/ast/VarAsBindingASTTransformation.groovy
@@ -115,6 +115,9 @@ class VarAsBindingASTTransformation implements ASTTransformation {
case GraphTraversal.Symbols.by:
if (i == 1) bindingValue = new PropertyExpression(new ClassExpression(new ClassNode(Order)), "desc")
break
+ case GraphTraversal.Symbols.fail:
+ if (i == 1) bindingValue = new MethodCallExpression(new ClassExpression(new ClassNode(Collections)), "emptyMap", new TupleExpression())
+ break
}
def bindingExpression = createBindingFromVar(entry.text, bindingVariableName, bindingValue)
bindingExpression.sourcePosition = entry
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
index e45e0e2..6c49ec5 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/process/graph-traversal.js
@@ -500,6 +500,16 @@ class GraphTraversal extends Traversal {
this.bytecode.addStep('emit', args);
return this;
}
+
+ /**
+ * Graph traversal fa method.
+ * @param {...Object} args
+ * @returns {GraphTraversal}
+ */
+ fail(...args) {
+ this.bytecode.addStep('fail', args);
+ return this;
+ }
/**
* Graph traversal filter method.
@@ -1325,6 +1335,7 @@ const statics = {
drop: (...args) => callOnEmptyTraversal('drop', args),
elementMap: (...args) => callOnEmptyTraversal('elementMap', args),
emit: (...args) => callOnEmptyTraversal('emit', args),
+ fail: (...args) => callOnEmptyTraversal('fail', args),
filter: (...args) => callOnEmptyTraversal('filter', args),
flatMap: (...args) => callOnEmptyTraversal('flatMap', args),
fold: (...args) => callOnEmptyTraversal('fold', args),
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
index dd5c562..5205f85 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/feature-steps.js
@@ -128,7 +128,7 @@ Given(/^using the parameter (.+) defined as "(.+)"$/, function (paramName, strin
});
When('iterated to list', function () {
- return this.traversal.toList().then(list => this.result = list);
+ return this.traversal.toList().then(list => this.result = list).catch(err => this.result = err);
});
When('iterated next', function () {
@@ -138,10 +138,16 @@ When('iterated next', function () {
// Compare using the objects array
this.result = this.result.objects;
}
- });
+ }).catch(err => this.result = err);
+});
+
+Then('the traversal will raise an error', function() {
+ expect(this.result).to.be.a.instanceof(Error);
});
Then(/^the result should be (\w+)$/, function assertResult(characterizedAs, resultTable) {
+ expect(this.result).to.not.be.a.instanceof(Error);
+
if (characterizedAs === 'empty') {
expect(this.result).to.be.empty;
if (typeof resultTable === 'function'){
@@ -165,6 +171,8 @@ Then(/^the result should be (\w+)$/, function assertResult(characterizedAs, resu
});
Then(/^the graph should return (\d+) for count of "(.+)"$/, function (stringCount, traversalText) {
+ expect(this.result).to.not.be.a.instanceof(Error);
+
const p = Object.assign({}, this.parameters);
p.g = this.g;
const traversal = gremlin[this.scenario].shift()(p);
@@ -174,6 +182,8 @@ Then(/^the graph should return (\d+) for count of "(.+)"$/, function (stringCoun
});
Then(/^the result should have a count of (\d+)$/, function (stringCount) {
+ expect(this.result).to.not.be.a.instanceof(Error);
+
const expected = parseInt(stringCount, 10);
if (!Array.isArray(this.result)) {
let count = 0;
diff --git a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
index 6f49924..d303f97 100644
--- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
+++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js
@@ -703,6 +703,9 @@ const gremlins = {
g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX: [function({g}) { return g.V().aggregate(Scope.local,"a").by(__.outE("created").count()).out().out().aggregate(Scope.local,"a").by(__.inE("created").values("weight").sum()).cap("a") }],
g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX: [function({g}) { return g.V().aggregate("x").by(__.values("age").is(P.gt(29))).cap("x") }],
g_V_aggregateXxX_byXout_order_byXnameXX_capXxX: [function({g}) { return g.V().aggregate("x").by(__.out().order().by("name")).cap("x") }],
+ g_V_fail: [function({g}) { return g.V().fail() }],
+ g_V_failXmsgX: [function({g}) { return g.V().fail("msg") }],
+ g_V_unionXout_failX: [function({g}) { return g.V().union(__.out(),__.fail()) }],
g_V_group_byXnameX: [function({g}) { return g.V().group().by("name") }],
g_V_group_byXageX: [function({g}) { return g.V().group().by("age") }],
g_V_group_byXnameX_by: [function({g}) { return g.V().group().by("name").by() }],
diff --git a/gremlin-language/src/main/antlr4/Gremlin.g4 b/gremlin-language/src/main/antlr4/Gremlin.g4
index a1ebb53..4cbc8a4 100644
--- a/gremlin-language/src/main/antlr4/Gremlin.g4
+++ b/gremlin-language/src/main/antlr4/Gremlin.g4
@@ -240,6 +240,7 @@ traversalMethod
| traversalMethod_subgraph
| traversalMethod_sum
| traversalMethod_tail
+ | traversalMethod_fail
| traversalMethod_timeLimit
| traversalMethod_times
| traversalMethod_to
@@ -668,6 +669,11 @@ traversalMethod_tail
| 'tail' LPAREN integerLiteral RPAREN #traversalMethod_tail_long
;
+traversalMethod_fail
+ : 'fail' LPAREN RPAREN #traversalMethod_fail_Empty
+ | 'fail' LPAREN stringLiteral RPAREN #traversalMethod_fail_String
+ ;
+
traversalMethod_timeLimit
: 'timeLimit' LPAREN integerLiteral RPAREN
;
diff --git a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
index b56988b..770821e 100644
--- a/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
+++ b/gremlin-python/src/main/python/gremlin_python/process/graph_traversal.py
@@ -253,6 +253,10 @@ class GraphTraversal(Traversal):
self.bytecode.add_step("emit", *args)
return self
+ def fail(self, *args):
+ self.bytecode.add_step("fail", *args)
+ return self
+
def filter_(self, *args):
self.bytecode.add_step("filter", *args)
return self
@@ -679,6 +683,10 @@ class __(object, metaclass=MagicType):
return cls.graph_traversal(None, None, Bytecode()).emit(*args)
@classmethod
+ def fail(cls, *args):
+ return cls.graph_traversal(None, None, Bytecode()).fail(*args)
+
+ @classmethod
def filter_(cls, *args):
return cls.graph_traversal(None, None, Bytecode()).filter_(*args)
@@ -1039,6 +1047,10 @@ def emit(*args):
return __.emit(*args)
+def fail(*args):
+ return __.fail(*args)
+
+
def filter_(*args):
return __.filter_(*args)
@@ -1355,6 +1367,8 @@ statics.add_static('elementMap', elementMap)
statics.add_static('emit', emit)
+statics.add_static('fail', fail)
+
statics.add_static('filter_', filter_)
statics.add_static('flatMap', flatMap)
diff --git a/gremlin-python/src/main/python/radish/feature_steps.py b/gremlin-python/src/main/python/radish/feature_steps.py
index 9fd8cf6..a1f76ba 100644
--- a/gremlin-python/src/main/python/radish/feature_steps.py
+++ b/gremlin-python/src/main/python/radish/feature_steps.py
@@ -95,8 +95,12 @@ def translate_traversal(step):
def iterate_the_traversal(step):
if step.context.ignore:
return
-
- step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.toList()))
+
+ try:
+ step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.toList()))
+ step.context.failed = False
+ except:
+ step.context.failed = True
@when("iterated next")
@@ -104,14 +108,24 @@ def next_the_traversal(step):
if step.context.ignore:
return
- step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.next()))
+ try:
+ step.context.result = list(map(lambda x: _convert_results(x), step.context.traversal.next()))
+ step.context.failed = False
+ except:
+ step.context.failed = True
+
+@then("the traversal will raise an error")
+def raise_an_error(step):
+ assert_that(step.context.failed, equal_to(True))
@then("the result should be {characterized_as:w}")
def assert_result(step, characterized_as):
if step.context.ignore:
return
+ assert_that(step.context.failed, equal_to(False))
+
if characterized_as == "empty": # no results
assert_that(len(step.context.result), equal_to(0))
elif characterized_as == "ordered": # results asserted in the order of the data table
@@ -129,6 +143,8 @@ def assert_side_effects(step, count, traversal_string):
if step.context.ignore:
return
+ assert_that(step.context.failed, equal_to(False))
+
p = step.context.traversal_params if hasattr(step.context, "traversal_params") else {}
p['g'] = step.context.g
t = step.context.traversals.pop(0)(**p)
@@ -141,6 +157,8 @@ def assert_count(step, count):
if step.context.ignore:
return
+ assert_that(step.context.failed, equal_to(False))
+
assert_that(len(list(step.context.result)), equal_to(count))
diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py
index 652d491..13900d6 100644
--- a/gremlin-python/src/main/python/radish/gremlin.py
+++ b/gremlin-python/src/main/python/radish/gremlin.py
@@ -688,6 +688,9 @@ world.gremlins = {
'g_V_aggregateXlocal_aX_byXoutEXcreatedX_countX_out_out_aggregateXlocal_aX_byXinEXcreatedX_weight_sumX': [(lambda g:g.V().aggregate(Scope.local,'a').by(__.outE('created').count()).out().out().aggregate(Scope.local,'a').by(__.inE('created').weight.sum_()).cap('a'))],
'g_V_aggregateXxX_byXvaluesXageX_isXgtX29XXX_capXxX': [(lambda g:g.V().aggregate('x').by(__.age.is_(P.gt(29))).cap('x'))],
'g_V_aggregateXxX_byXout_order_byXnameXX_capXxX': [(lambda g:g.V().aggregate('x').by(__.out().order().by('name')).cap('x'))],
+ 'g_V_fail': [(lambda g:g.V().fail())],
+ 'g_V_failXmsgX': [(lambda g:g.V().fail('msg'))],
+ 'g_V_unionXout_failX': [(lambda g:g.V().union(__.out(),__.fail()))],
'g_V_group_byXnameX': [(lambda g:g.V().group().by('name'))],
'g_V_group_byXageX': [(lambda g:g.V().group().by('age'))],
'g_V_group_byXnameX_by': [(lambda g:g.V().group().by('name').by())],
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
index 65c26be..ed63dc6 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/handler/AbstractSession.java
@@ -35,6 +35,7 @@ import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException
import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptEngine;
import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.GraphOp;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
@@ -278,14 +279,20 @@ public abstract class AbstractSession implements Session, AutoCloseable {
protected void handleException(final SessionTask sessionTask, final Throwable t) throws SessionException {
if (t instanceof SessionException) throw (SessionException) t;
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(t);
- if (possibleTemporaryException.isPresent()) {
- final Throwable temporaryException = possibleTemporaryException.get();
- throw new SessionException(temporaryException.getMessage(), t,
- ResponseMessage.build(sessionTask.getRequestMessage())
- .code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(temporaryException.getMessage())
- .statusAttributeException(temporaryException).create());
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(sessionTask.getRequestMessage()).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ throw new SessionException(special.getMessage(), specialResponseMsg.create());
}
final Throwable root = ExceptionUtils.getRootCause(t);
@@ -373,12 +380,12 @@ public abstract class AbstractSession implements Session, AutoCloseable {
}
/**
- * Check if any exception in the chain is TemporaryException then we should respond with the right error code so
- * that the client knows to retry.
+ * Check if any exception in the chain is {@link TemporaryException} or {@link Failure} then respond with the
+ * right error code so that the client knows to retry.
*/
- protected Optional<Throwable> determineIfTemporaryException(final Throwable ex) {
+ protected static Optional<Throwable> determineIfSpecialException(final Throwable ex) {
return Stream.of(ExceptionUtils.getThrowables(ex)).
- filter(i -> i instanceof TemporaryException).findFirst();
+ filter(i -> i instanceof TemporaryException || i instanceof Failure).findFirst();
}
/**
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
index 2a8ecbe..cbd14a1 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractEvalOpProcessor.java
@@ -25,6 +25,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor;
import org.apache.tinkerpop.gremlin.groovy.jsr223.TimedInterruptTimeoutException;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.Operator;
import org.apache.tinkerpop.gremlin.process.traversal.Order;
import org.apache.tinkerpop.gremlin.process.traversal.Pop;
@@ -38,6 +39,7 @@ import org.apache.tinkerpop.gremlin.server.Context;
import org.apache.tinkerpop.gremlin.server.GremlinServer;
import org.apache.tinkerpop.gremlin.server.Settings;
import org.apache.tinkerpop.gremlin.server.util.MetricManager;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
@@ -272,13 +274,22 @@ public abstract class AbstractEvalOpProcessor extends AbstractOpProcessor {
timerContext.stop();
if (t != null) {
- // if any exception in the chain is TemporaryException then we should respond with the right error
- // code so that the client knows to retry
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(t);
- if (possibleTemporaryException.isPresent()) {
- ctx.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(t);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ ctx.writeAndFlush(specialResponseMsg.create());
} else if (t instanceof OpProcessorException) {
ctx.writeAndFlush(((OpProcessorException) t).getResponseMessage());
} else if (t instanceof TimedInterruptTimeoutException) {
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
index 1498c51..c11af3b 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/AbstractOpProcessor.java
@@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.driver.message.RequestMessage;
import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
import org.apache.tinkerpop.gremlin.driver.ser.MessageTextSerializer;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.server.Context;
import org.apache.tinkerpop.gremlin.server.GraphManager;
import org.apache.tinkerpop.gremlin.server.OpProcessor;
@@ -67,12 +68,12 @@ public abstract class AbstractOpProcessor implements OpProcessor {
}
/**
- * Check if any exception in the chain is TemporaryException then we should respond with the right error code so
- * that the client knows to retry.
+ * Check if any exception in the chain is {@link TemporaryException} or {@link Failure} then respond with the
+ * right error code so that the client knows to retry.
*/
- protected static Optional<Throwable> determineIfTemporaryException(final Throwable ex) {
+ protected static Optional<Throwable> determineIfSpecialException(final Throwable ex) {
return Stream.of(ExceptionUtils.getThrowables(ex)).
- filter(i -> i instanceof TemporaryException).findFirst();
+ filter(i -> i instanceof TemporaryException || i instanceof Failure).findFirst();
}
/**
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
index 6a18645..a5df693 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/session/SessionOpProcessor.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
import org.apache.tinkerpop.gremlin.groovy.jsr223.GroovyCompilerGremlinPlugin;
import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.util.BytecodeHelper;
@@ -46,6 +47,7 @@ import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
import org.apache.tinkerpop.gremlin.structure.Graph;
import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONMapper;
import org.apache.tinkerpop.gremlin.structure.io.graphson.GraphSONVersion;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
import org.apache.tinkerpop.shaded.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
@@ -419,13 +421,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
if (ex instanceof UndeclaredThrowableException)
t = t.getCause();
- // if any exception in the chain is TemporaryException then we should respond with the right error
- // code so that the client knows to retry
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
- if (possibleTemporaryException.isPresent()) {
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ context.writeAndFlush(specialResponseMsg.create());
} else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
logger.warn(errorMessage);
@@ -441,11 +452,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
onError(graph, context);
}
} catch (Exception ex) {
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
- if (possibleTemporaryException.isPresent()) {
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ context.writeAndFlush(specialResponseMsg.create());
} else {
logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
@@ -501,11 +523,22 @@ public class SessionOpProcessor extends AbstractEvalOpProcessor {
.create());
} catch (Exception ex) {
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
- if (possibleTemporaryException.isPresent()) {
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ context.writeAndFlush(specialResponseMsg.create());
} else {
logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
diff --git a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
index 80be01f..620332f 100644
--- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
+++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/op/traversal/TraversalOpProcessor.java
@@ -27,6 +27,7 @@ import org.apache.tinkerpop.gremlin.driver.message.ResponseMessage;
import org.apache.tinkerpop.gremlin.driver.message.ResponseStatusCode;
import org.apache.tinkerpop.gremlin.jsr223.JavaTranslator;
import org.apache.tinkerpop.gremlin.process.traversal.Bytecode;
+import org.apache.tinkerpop.gremlin.process.traversal.Failure;
import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
import org.apache.tinkerpop.gremlin.process.traversal.TraversalSource;
import org.apache.tinkerpop.gremlin.process.traversal.util.BytecodeHelper;
@@ -38,13 +39,13 @@ import org.apache.tinkerpop.gremlin.server.OpProcessor;
import org.apache.tinkerpop.gremlin.server.Settings;
import org.apache.tinkerpop.gremlin.server.auth.AuthenticatedUser;
import org.apache.tinkerpop.gremlin.server.handler.Frame;
-import org.apache.tinkerpop.gremlin.server.handler.SessionException;
import org.apache.tinkerpop.gremlin.server.handler.StateKey;
import org.apache.tinkerpop.gremlin.server.op.AbstractOpProcessor;
import org.apache.tinkerpop.gremlin.server.op.OpProcessorException;
import org.apache.tinkerpop.gremlin.server.util.MetricManager;
import org.apache.tinkerpop.gremlin.server.util.TraverserIterator;
import org.apache.tinkerpop.gremlin.structure.Graph;
+import org.apache.tinkerpop.gremlin.structure.util.TemporaryException;
import org.apache.tinkerpop.gremlin.util.function.ThrowingConsumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -222,13 +223,22 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
if (ex instanceof UndeclaredThrowableException)
t = t.getCause();
- // if any exception in the chain is TemporaryException then we should respond with the right error
- // code so that the client knows to retry
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
- if (possibleTemporaryException.isPresent()) {
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ context.writeAndFlush(specialResponseMsg.create());
} else if (t instanceof InterruptedException || t instanceof TraversalInterruptedException) {
final String errorMessage = String.format("A timeout occurred during traversal evaluation of [%s] - consider increasing the limit given to evaluationTimeout", msg);
logger.warn(errorMessage);
@@ -244,11 +254,22 @@ public class TraversalOpProcessor extends AbstractOpProcessor {
onError(graph, context);
}
} catch (Exception ex) {
- final Optional<Throwable> possibleTemporaryException = determineIfTemporaryException(ex);
- if (possibleTemporaryException.isPresent()) {
- context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR_TEMPORARY)
- .statusMessage(possibleTemporaryException.get().getMessage())
- .statusAttributeException(possibleTemporaryException.get()).create());
+ // if any exception in the chain is TemporaryException or Failure then we should respond with the
+ // right error code so that the client knows to retry
+ final Optional<Throwable> possibleSpecialException = determineIfSpecialException(ex);
+ if (possibleSpecialException.isPresent()) {
+ final Throwable special = possibleSpecialException.get();
+ final ResponseMessage.Builder specialResponseMsg = ResponseMessage.build(msg).
+ statusMessage(special.getMessage()).
+ statusAttributeException(special);
+ if (special instanceof TemporaryException) {
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_TEMPORARY);
+ } else if (special instanceof Failure) {
+ final Failure failure = (Failure) special;
+ specialResponseMsg.code(ResponseStatusCode.SERVER_ERROR_FAIL_STEP).
+ statusAttribute(Tokens.STATUS_ATTRIBUTE_FAIL_STEP_MESSAGE, failure.format());
+ }
+ context.writeAndFlush(specialResponseMsg.create());
} else {
logger.warn(String.format("Exception processing a Traversal on request [%s].", msg.getRequestId()), ex);
context.writeAndFlush(ResponseMessage.build(msg).code(ResponseStatusCode.SERVER_ERROR)
diff --git a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
index 2f88ddc..dd9b682 100644
--- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
+++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerIntegrateTest.java
@@ -1083,4 +1083,20 @@ public class GremlinServerIntegrateTest extends AbstractGremlinServerIntegration
assertEquals(ResponseStatusCode.SERVER_ERROR_TEMPORARY, ((ResponseException) t).getResponseStatusCode());
}
}
+
+ @Test
+ public void shouldGenerateFailureErrorResponseStatusCode() throws Exception {
+ final Cluster cluster = TestClientFactory.build().create();
+ final Client client = cluster.connect();
+
+ try {
+ client.submit("g.inject(0).fail('make it stop')").all().get();
+ fail("Should have tanked since we used fail() step");
+ } catch (Exception ex) {
+ final Throwable t = ex.getCause();
+ assertThat(t, instanceOf(ResponseException.class));
+ assertEquals("make it stop", t.getMessage());
+ assertEquals(ResponseStatusCode.SERVER_ERROR_FAIL_STEP, ((ResponseException) t).getResponseStatusCode());
+ }
+ }
}
diff --git a/gremlin-test/features/sideEffect/Fail.feature b/gremlin-test/features/sideEffect/Fail.feature
new file mode 100644
index 0000000..53f2d56
--- /dev/null
+++ b/gremlin-test/features/sideEffect/Fail.feature
@@ -0,0 +1,46 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+@StepClassSideEffect @StepFail
+Feature: Step - fail()
+
+ Scenario: g_V_fail
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().fail()
+ """
+ When iterated to list
+ Then the traversal will raise an error
+
+ Scenario: g_V_failXmsgX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().fail("msg")
+ """
+ When iterated to list
+ Then the traversal will raise an error
+
+ Scenario: g_V_unionXout_failX
+ Given the modern graph
+ And the traversal of
+ """
+ g.V().union(out(), fail())
+ """
+ When iterated to list
+ Then the traversal will raise an error
diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
index 6b644a0..daeff68 100644
--- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
+++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/features/StepDefinition.java
@@ -53,6 +53,7 @@ import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.hamcrest.core.Every.everyItem;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import java.math.BigDecimal;
@@ -81,6 +82,7 @@ public final class StepDefinition {
private final Map<String, String> stringParameters = new HashMap<>();
private Traversal traversal;
private Object result;
+ private Throwable error;
private static final Pattern edgeTripletPattern = Pattern.compile("(.+)-(.+)->(.+)");
private static final Pattern ioPattern = Pattern.compile("g\\.io\\(\"(.*)\"\\).*");
private List<Pair<Pattern, Function<String,String>>> stringMatcherConverters = new ArrayList<Pair<Pattern, Function<String,String>>>() {{
@@ -204,6 +206,7 @@ public final class StepDefinition {
}
if (result != null) result = null;
+ if (error != null) error = null;
}
@After
@@ -243,16 +246,26 @@ public final class StepDefinition {
@When("iterated to list")
public void iteratedToList() {
- result = traversal.toList();
+ try {
+ result = traversal.toList();
+ } catch (Exception ex) {
+ error = ex;
+ }
}
@When("iterated next")
public void iteratedNext() {
- result = traversal.next();
+ try {
+ result = traversal.next();
+ } catch (Exception ex) {
+ error = ex;
+ }
}
@Then("the result should be unordered")
public void theResultShouldBeUnordered(final DataTable dataTable) {
+ assertThatNoErrorWasThrown();
+
final List<Object> actual = translateResultsToActual();
// account for header in dataTable size
@@ -265,6 +278,8 @@ public final class StepDefinition {
@Then("the result should be ordered")
public void theResultShouldBeOrdered(final DataTable dataTable) {
+ assertThatNoErrorWasThrown();
+
final List<Object> actual = translateResultsToActual();
// account for header in dataTable size
@@ -277,6 +292,9 @@ public final class StepDefinition {
@Then("the result should be of")
public void theResultShouldBeOf(final DataTable dataTable) {
+ assertThatNoErrorWasThrown();
+
+
final List<Object> actual = translateResultsToActual();
// skip the header in the dataTable
@@ -286,6 +304,8 @@ public final class StepDefinition {
@Then("the result should have a count of {int}")
public void theResultShouldHaveACountOf(final Integer val) {
+ assertThatNoErrorWasThrown();
+
if (result instanceof Iterable)
assertEquals(val.intValue(), IteratorUtils.count((Iterable) result));
else if (result instanceof Map)
@@ -296,15 +316,27 @@ public final class StepDefinition {
@Then("the graph should return {int} for count of {string}")
public void theGraphShouldReturnForCountOf(final Integer count, final String gremlin) {
+ assertThatNoErrorWasThrown();
+
assertEquals(count.longValue(), ((GraphTraversal) parseGremlin(applyParameters(gremlin))).count().next());
}
@Then("the result should be empty")
public void theResultShouldBeEmpty() {
+ assertThatNoErrorWasThrown();
+
assertThat(result, instanceOf(Collection.class));
assertEquals(0, IteratorUtils.count((Collection) result));
}
+ @Then("the traversal will raise an error")
+ public void theTraversalWillRaiseAnError() {
+ assertNotNull(error);
+
+ // consume the error now that it has been asserted
+ error = null;
+ }
+
//////////////////////////////////////////////
@Given("an unsupported test")
@@ -319,6 +351,10 @@ public final class StepDefinition {
//////////////////////////////////////////////
+ private void assertThatNoErrorWasThrown() {
+ if (error != null) throw new RuntimeException(error);
+ }
+
private Traversal parseGremlin(final String script) {
final GremlinLexer lexer = new GremlinLexer(CharStreams.fromString(script));
final GremlinParser parser = new GremlinParser(new CommonTokenStream(lexer));