This is an automated email from the ASF dual-hosted git repository. colegreer pushed a commit to branch TINKERPOP-3173 in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit 358e4ea143c75aeaec294ffac0c2884ed21da715 Author: Cole-Greer <[email protected]> AuthorDate: Tue Jul 15 12:13:23 2025 -0700 TINKERPOP-3173 Simplify comparability semantics Replaces the old system of ternary boolean semantics with simple binary semantics. The triggers for "ERROR" states from illegal comparisons are unchanged (typically comparisons with NaN or between incomparable types such as String and int). The difference now is that instead of the ERROR being propagated according to ternary logic semantics until a reduction point is reached, the error now immediately returns a value of FALSE. This will be most visible in expressions which include negations. Prior to this change, `g.inject(NaN).not(is(1))` would produce no results as !(NaN == 1) -> !(ERROR) -> ERROR -> traverser is filtered out. After this change, the same traversal will return NaN as the same expression now evaluates as !(NaN == 1) -> !(FALSE) -> TRUE -> traverser is not filtered. This commit also introduces NotP to model a negated P, as the old method of getting the complement of the PBiPredicate may handle edge cases incorrectly: !(1 < NaN) != (1 >= NaN) -> !(FALSE) != (FALSE) --- CHANGELOG.asciidoc | 2 + docs/src/dev/provider/gremlin-semantics.asciidoc | 87 +++------------- docs/src/upgrade/release-3.8.x.asciidoc | 25 +++++ .../gremlin/process/traversal/Compare.java | 65 +++--------- .../gremlin/process/traversal/Contains.java | 20 +--- .../traversal/GremlinTypeErrorException.java | 40 -------- .../tinkerpop/gremlin/process/traversal/NotP.java | 110 ++++++++++++++++++++ .../tinkerpop/gremlin/process/traversal/P.java | 2 +- .../gremlin/process/traversal/PBiPredicate.java | 7 -- .../tinkerpop/gremlin/process/traversal/Text.java | 11 +- .../tinkerpop/gremlin/process/traversal/TextP.java | 4 +- .../process/traversal/step/filter/AllStep.java | 12 +-- .../process/traversal/step/filter/AndStep.java | 13 +-- .../process/traversal/step/filter/AnyStep.java | 12 +-- .../traversal/step/filter/BinaryReductionStep.java | 29 ------ .../process/traversal/step/filter/FilterStep.java | 22 +--- .../process/traversal/step/filter/NoneStep.java | 12 +-- .../process/traversal/step/filter/OrStep.java | 13 +-- .../traversal/step/filter/TraversalFilterStep.java | 2 +- .../traversal/step/filter/WhereTraversalStep.java | 2 +- .../strategy/optimization/CountStrategy.java | 7 +- .../traversal/translator/GroovyTranslator.java | 4 + .../gremlin/process/traversal/util/AndP.java | 13 +-- .../gremlin/process/traversal/util/OrP.java | 14 +-- .../io/binary/TypeSerializerRegistry.java | 2 + .../structure/io/binary/types/PSerializer.java | 10 +- .../structure/io/graphson/GraphSONModule.java | 3 + .../io/graphson/TraversalSerializersV2.java | 3 + .../io/graphson/TraversalSerializersV3.java | 3 + .../structure/io/gryo/GryoClassResolverV1.java | 3 + .../structure/io/gryo/GryoClassResolverV3.java | 3 + .../structure/io/gryo/GryoSerializersV1.java | 27 +++-- .../structure/io/gryo/GryoSerializersV3.java | 27 +++-- .../gremlin/util/GremlinValueComparator.java | 97 ++++++++++++------ .../grammar/TraversalPredicateVisitorTest.java | 40 ++++---- .../gremlin/process/traversal/CompareTest.java | 114 ++++++++++----------- .../gremlin/process/traversal/ConnectiveTest.java | 16 --- .../tinkerpop/gremlin/process/traversal/PTest.java | 35 ++++--- .../traversal/step/filter/NoneStepTest.java | 7 +- .../Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs | 2 +- gremlin-go/driver/cucumber/gremlin.go | 2 +- .../gremlin-javascript/test/cucumber/gremlin.js | 2 +- gremlin-python/src/main/python/radish/gremlin.py | 2 +- .../process/ProcessEmbeddedStandardSuite.java | 4 +- .../gremlin/process/ProcessStandardSuite.java | 4 +- ...csTest.java => ComparabilitySemanticsTest.java} | 42 +++----- .../test/features/semantics/Comparability.feature | 6 +- .../traversal/step/sideEffect/TinkerGraphStep.java | 19 +--- 48 files changed, 456 insertions(+), 545 deletions(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 88540a25b3..22787d44e2 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -83,6 +83,8 @@ This release also includes changes from <<release-3-7-XXX, 3.7.XXX>>. * Deprecated `ProcessStandardSuite` and the `ProcessComputerSuite` in favor of Gherkin testing and the `ProcessEmbeddedStandardSuite` and `ProcessEmbeddedComputerSuite` for testing JVM-specific Gremlin behaviors. * Removed lambda oriented Gremlin testing from Gherkin test suite. * Removed `P.getOriginalValue()` in favor of `P.getValue()` +* Simplified comparability semantics from ternary boolean logic to binary logic. +* Introduced `NotP` class for negation of `P` * Increase minimum Java version from 1.8 to 11 for building and running. * Moved all lambda oriented Gremlin tests to `LambdaStepTest` in the Java test suite. * Removed the `@RemoteOnly` testing tag in Gherkin as lambda tests have all been moved to the Java test suite. diff --git a/docs/src/dev/provider/gremlin-semantics.asciidoc b/docs/src/dev/provider/gremlin-semantics.asciidoc index fa3d9dbcae..8d5563a72b 100644 --- a/docs/src/dev/provider/gremlin-semantics.asciidoc +++ b/docs/src/dev/provider/gremlin-semantics.asciidoc @@ -144,67 +144,10 @@ Key differences include handling of numeric types and NaN. Both Equality and Equivalence can be understood as complete, i.e. the result of equality and equivalence checks is always either `TRUE` or `FALSE` (in particular, it never returns `nulltype` or throws an exception). Similarly, Orderability can be also understood as complete - any two values can be compared without error for ordering purposes. -Comparability semantics are not complete with respect to binary boolean semantics, and as such, Gremlin introduces a -ternary boolean semantics for Comparability that includes a third boolean state - `ERROR`, with its own well-defined -semantics. - -=== Ternary Boolean Logics - -When evaluating boolean value expressions, we sometimes encounter situations that cannot be proved as either `TRUE` or -`FALSE`. Common `ERROR` cases are Comparability against `NaN`, cross-type Comparability (e.g. `String` vs `Numeric`), or -other invalid arguments to other boolean value expressions. - -Rather than throwing an exception and halting the traversal, we extend normal binary boolean logics and introduce a -third boolean option - `ERROR`. How this `ERROR` result is handled is Graph provider dependent. For the reference -implementation, `ERROR` is an internal representation only and will not be propagated back to the client as an -exception - it will eventually hit a binary reduction operation and be reduced to `FALSE` (thus quietly filters the -solution that produced the `ERROR`). Before that happens though, it will be treated as its own boolean value with its -own semantics that can be used in other boolean value expressions, such as Connective predicates (`P.and/or`) and -negation (`P.not`). - -==== Ternary Boolean Semantics for `AND` - -|=== -|A|B|AND|Intuition - -| `TRUE` | `TRUE` | `TRUE` | -| `TRUE` | `FALSE` | `FALSE` | -| `TRUE` | `ERROR` | `ERROR` | `TRUE && X == X` -| `FALSE` | `TRUE` | `FALSE` | -| `FALSE` | `FALSE` | `FALSE` | -| `FALSE` | `ERROR` | `FALSE` | `FALSE && X == FALSE` -| `ERROR` | `TRUE` | `ERROR` | `X && TRUE == X` -| `ERROR` | `FALSE` | `FALSE` | `X && FALSE == FALSE` -| `ERROR` | `ERROR` | `ERROR` | `X && X == X` -|=== - -==== Ternary Boolean Semantics for `OR` - -|=== -|A|B|OR|Intuition - -| `TRUE` | `TRUE` | `TRUE` | -| `TRUE` | `FALSE` | `TRUE` | -| `TRUE` | `ERROR` | `TRUE` | `TRUE \|\| X == TRUE` -| `FALSE` | `TRUE` | `TRUE` | -| `FALSE` | `FALSE` | `FALSE` | -| `FALSE` | `ERROR` | `ERROR` | `FALSE \|\| X == X` -| `ERROR` | `TRUE` | `TRUE` | `X \|\| TRUE == TRUE` -| `ERROR` | `FALSE` | `ERROR` | `X \|\| FALSE == X` -| `ERROR` | `ERROR` | `ERROR` | `X \|\| X == X` -|=== - -==== Ternary Boolean Semantics for `NOT` - -The `NOT` predicate inverts `TRUE` and `FALSE`, respectively, but maintains `ERROR` values. The key idea is that, for an -`ERROR` value, we can neither prove nor disprove the expression, and hence stick with `ERROR`. -|=== -|Argument | Result - -|`TRUE` | `FALSE` -|`FALSE` | `TRUE` -|`ERROR` | `ERROR` -|=== +Comparability semantics are not complete with respect to traditional binary boolean semantics, as certain comparisons +cannot be proved as either `TRUE` or `FALSE`. Common examples of such cases are Comparability against `NaN`, cross-type +Comparability (e.g. `String` vs `Numeric`). For the purposes of Comparability within Gremlin, any such incomputable +comparison is defined to be `FALSE`, and traditional binary boolean semantics apply thereafter. [[gremlin-semantics-equality-comparability]] === Equality and Comparability @@ -243,12 +186,12 @@ classes of comparison: |Comparisons Involving `NaN`|`(NaN,X)` + -where `X` = any value, including `NaN`|`ERROR` + +where `X` = any value, including `NaN`|`FALSE` + Comparing `NaN` to anything (including itself) cannot be evaluated.|`FALSE` |Comparisons Involving `null`|`(null,null)`|`compare() == 0`|`TRUE` -||`(null, X)`|`ERROR` + +||`(null, X)`|`FALSE` + Since `nulltype` is its own type, this falls under the umbrella of cross-type comparisons. |`FALSE` |Comparisons within the same type family (i.e. String vs. String, Number vs. Number, etc.)|`(X, Y)` + @@ -256,7 +199,7 @@ Since `nulltype` is its own type, this falls under the umbrella of cross-type co where `X` and `Y` of same type|Result of `compare()` depends on type semantics, defined below.|`TRUE` iff `compare() == 0` |Comparisons across types (i.e. String vs. Number)|`(X, Y)` + -where `X` and `Y` of different type|`ERROR`|`FALSE` +where `X` and `Y` of different type|`FALSE`|`FALSE` |=== @@ -316,8 +259,8 @@ not <<Equality,Equal>> and not <<Comparability,Comparable>>. ===== List Lists are compared pairwise, element-by-element, in their natural list order. For each element, if the pairs are -<<Equality and Comparability,Equal>>, we simply move on to the next element pair until we encounter a pair whose -`Comparability.compare()` value is non-zero (`-1`, `1`, or `ERROR`), and we return that value. Lists can be evaluated +<<Equality and Comparability,Equal>>, we simply move on to the next element pair until we encounter a pair which is not +comparable, or whose `Comparability.compare()` value is non-zero, and we return that value. Lists can be evaluated for <<Equality and Comparability,Equality and Comparability>> even if they contain multiple types of elements, so long as their elements are pairwise comparable per <<Equality and Comparability,Equality/Comparability>> semantics. During this element by element comparison, if iteration `A` exhausts its elements before iteration `B` then `A < B`, and @@ -400,7 +343,7 @@ treat pairwise entries with semantically equivalent keys as the same. <<Equality and Comparability,Equality and Comparability>> were described in depth in the sections above, and their semantics map to the `P` predicates. <<Comparability,Comparability>> in particular is limited to comparison of values within the same type family. Comparability is complete within a given type (except for `NaN`, -which results in `ERROR` for any comparison), but returns `ERROR` for comparisons across types (e.g., an integer cannot +which results in `FALSE` for any comparison), but returns `FALSE` for comparisons across types (e.g., an integer cannot be compared to a string). Orderability semantics are very similar to Comparability for the most part, except that Orderability will never result @@ -409,8 +352,8 @@ we will still be able to determine their respective order. This allows for a tot the reference implementation, any step using `Order.asc` or `Order.desc` (e.g. OrderGlobalStep, OrderLocalStep) will follow these semantics. -To achieve this globally complete order, we need to address any cases in Comparability that produce a comparison -`ERROR`, we must define a global order across type families, and we must provide semantics for ordering "unknown" +To achieve this globally complete order, we need to address any cases in Comparability which are incomputable, we must +define a global order across type families, and we must provide semantics for ordering "unknown" values (for cases of in-process JVM implementations, like the TinkerGraph). We define the type space, and the global order across the type space as follows: @@ -439,11 +382,11 @@ Within a given type space, Orderability determines if two values are ordered at positioned before or after the another. When the position is identical, which value comes first (in other words, whether it should perform stable sort) depends on graph providers' implementation. -To allow for this total ordering, we must also address the cases in <<Equality and Comparability,Comparability>> that -produce an comparison `ERROR`: +To allow for this total ordering, we must also address the cases in <<Equality and Comparability,Comparability>> where +the comparison is incomputable: |=== -|`ERROR` Scenario|Comparability|Orderability +|Incomputable Scenario|Comparability|Orderability |Comparison against `NaN`|`NaN` not comparable to anything, including itself.|`NaN` appears after `+Infinity` in the numeric type space. |Comparison across types|Cannot compare values of different types. This includes the `nulltype`.|Subject to a total diff --git a/docs/src/upgrade/release-3.8.x.asciidoc b/docs/src/upgrade/release-3.8.x.asciidoc index 66ea41039e..215fd38c2b 100644 --- a/docs/src/upgrade/release-3.8.x.asciidoc +++ b/docs/src/upgrade/release-3.8.x.asciidoc @@ -65,6 +65,22 @@ version, the `none()` step was used to "throw away" all traversers that passed i renamed to `discard()`. The `discard()` step with its verb tone arguably makes for a better name for that feature, but it also helped make room for `none()` to be repurposed as `none(P)` which is a complement to `any(P)` and `all(P) steps. +==== Simplified Comparability Semantics + +The previous system of ternary boolean semantics has been replaced with simplified binary semantics. The triggers for +"ERROR" states from illegal comparisons are unchanged (typically comparisons with NaN or between incomparable types +such as String and int). The difference now is that instead of the ERROR being propagated according to ternary logic +semantics until a reduction point is reached, the error now immediately returns a value of FALSE. + +This will be most visible in expressions which include negations. Prior to this change, `g.inject(NaN).not(is(1))` would +produce no results as `!(NaN == 1)` -> `!(ERROR)` -> `ERROR` -> traverser is filtered out. After this change, the same +traversal will return NaN as the same expression now evaluates as `!(NaN == 1)` -> `!(FALSE)` -> `TRUE` -> traverser is +not filtered. + +See: link:https://tinkerpop.apache.org/docs/3.8.0/dev/provider/#gremlin-semantics-equality-comparability[Comparability semantics docs] + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3173[TINKERPOP-3173] + ==== Set minimum Java version to 11 TinkerPop 3.8 requires a minimum of Java 11 for building and running. Support for Java 1.8 has been dropped. @@ -574,6 +590,15 @@ The `DiscardStep` is now renamed to `DiscardStep`. Providers who developed strat `DiscardStep` should switch to `DiscardStep`. Note that `DiscardStep` has been repurposed as `none(P)` for filtering collections as a complement to `any(P)` and `all(P)`. +===== Added `NotP` + +Added a new subclass of `P` to model negated predicates. This has been introduced as the previous system of taking the +complementary PBiPredicate during negation does not account for edge cases involving illegal comparisons: + +`!(1 < NaN) != (1 >= NaN)` -> `!(FALSE) != (FALSE)` + +See: link:https://issues.apache.org/jira/browse/TINKERPOP-3173[TINKERPOP-3173] + ===== Set minimum Java version to 11 TinkerPop 3.8 requires a minimum of Java 11 for building and running. Support for Java 1.8 has been dropped. diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java index 09387614f8..a2f94d22f3 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java @@ -41,14 +41,6 @@ public enum Compare implements PBiPredicate<Object, Object> { public boolean test(final Object first, final Object second) { return GremlinValueComparator.COMPARABILITY.equals(first, second); } - - /** - * The negative of {@code eq} is {@link #neq}. - */ - @Override - public Compare negate() { - return neq; - } }, /** @@ -61,14 +53,6 @@ public enum Compare implements PBiPredicate<Object, Object> { public boolean test(final Object first, final Object second) { return !eq.test(first, second); } - - /** - * The negative of {@code neq} is {@link #eq} - */ - @Override - public Compare negate() { - return eq; - } }, /** @@ -79,16 +63,11 @@ public enum Compare implements PBiPredicate<Object, Object> { gt { @Override public boolean test(final Object first, final Object second) { + if (!GremlinValueComparator.COMPARABILITY.comparable(first, second)) { + return false; + } return GremlinValueComparator.COMPARABILITY.compare(first, second) > 0; } - - /** - * The negative of {@code gt} is {@link #lte}. - */ - @Override - public Compare negate() { - return lte; - } }, /** @@ -99,16 +78,11 @@ public enum Compare implements PBiPredicate<Object, Object> { gte { @Override public boolean test(final Object first, final Object second) { + if (!GremlinValueComparator.COMPARABILITY.comparable(first, second)) { + return false; + } return GremlinValueComparator.COMPARABILITY.compare(first, second) >= 0; } - - /** - * The negative of {@code gte} is {@link #lt}. - */ - @Override - public Compare negate() { - return lt; - } }, /** @@ -119,16 +93,11 @@ public enum Compare implements PBiPredicate<Object, Object> { lt { @Override public boolean test(final Object first, final Object second) { + if (!GremlinValueComparator.COMPARABILITY.comparable(first, second)) { + return false; + } return GremlinValueComparator.COMPARABILITY.compare(first, second) < 0; } - - /** - * The negative of {@code lt} is {@link #gte}. - */ - @Override - public Compare negate() { - return gte; - } }, /** @@ -139,21 +108,11 @@ public enum Compare implements PBiPredicate<Object, Object> { lte { @Override public boolean test(final Object first, final Object second) { + if (!GremlinValueComparator.COMPARABILITY.comparable(first, second)) { + return false; + } return GremlinValueComparator.COMPARABILITY.compare(first, second) <= 0; } - - /** - * The negative of {@code lte} is {@link #gt}. - */ - @Override - public Compare negate() { - return gt; - } }; - /** - * Produce the opposite representation of the current {@code Compare} enum. - */ - @Override - public abstract Compare negate(); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java index 3231f60dbd..bfdc9f9c10 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java @@ -64,18 +64,10 @@ public enum Contains implements PBiPredicate<Object, Collection> { */ return second.contains(first); } - GremlinTypeErrorException typeError = null; for (final Object o : second) { - try { - if (Compare.eq.test(first, o)) - return true; - } catch (GremlinTypeErrorException ex) { - // hold onto it until the end in case any other arguments evaluate to TRUE - typeError = ex; - } + if (Compare.eq.test(first, o)) + return true; } - if (typeError != null) - throw typeError; return false; } }, @@ -98,12 +90,4 @@ public enum Contains implements PBiPredicate<Object, Collection> { */ @Override public abstract boolean test(final Object first, final Collection second); - - /** - * Produce the opposite representation of the current {@code Contains} enum. - */ - @Override - public Contains negate() { - return this.equals(within) ? without : within; - } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java deleted file mode 100644 index 063a759ef3..0000000000 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/GremlinTypeErrorException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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; - -/** - * We use this exception type to signify ERROR in Ternary Boolean Logics. This exception will never propagate to the - * user for boolean value expressions, it will always be ultimately reduced to FALSE. - */ -public class GremlinTypeErrorException extends RuntimeException { - public GremlinTypeErrorException() { - } - - public GremlinTypeErrorException(final String message) { - super(message); - } - - public GremlinTypeErrorException(final String message, final Throwable cause) { - super(message, cause); - } - - public GremlinTypeErrorException(final Throwable cause) { - super(cause); - } -} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/NotP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/NotP.java new file mode 100644 index 0000000000..1286761959 --- /dev/null +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/NotP.java @@ -0,0 +1,110 @@ +/* + * 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 java.io.Serializable; +import java.util.Objects; + +public class NotP<V> extends P<V> { + private P<V> originalP; + public NotP(final P<V> p) { + super(null, (V) null); + + if (null == p) { + throw new IllegalArgumentException("Cannot negate a null P"); + } + this.originalP = p; + this.biPredicate = new NotPBiPredicate<>(p.getBiPredicate()); + } + + /** + * Gets the current value to be passed to the predicate for testing. + */ + @Override + public V getValue() { + return originalP.getValue(); + } + + @Override + public void setValue(final V value) { + super.setValue(value); + if (originalP != null) { + originalP.setValue(value); + } + } + + @Override + public String toString() { + return null == this.getValue() ? this.biPredicate.toString() : String.format("not(%s(%s))", this.originalP.biPredicate.toString(), this.getValue()); + } + + @Override + public P<V> negate() { + return originalP; + } + + public P<V> clone() { + return new NotP<>(this.originalP.clone()); + } + + public static class NotPBiPredicate<T, U> implements PBiPredicate<T, U>, Serializable { + PBiPredicate<T, U> original; + + public NotPBiPredicate(PBiPredicate<T, U> predicate) { + this.original = predicate; + } + + @Override + public boolean test(T t, U u) { + return !original.test(t, u); + } + + @Override + public PBiPredicate<T, U> negate() { + return original; + } + + @Override + public String getPredicateName() { + return "not"; + } + + public PBiPredicate<T, U> getOriginal() { + return original; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NotPBiPredicate<?, ?> that = (NotPBiPredicate<?, ?>) o; + return Objects.equals(original, that.original); + } + + @Override + public int hashCode() { + return Objects.hashCode(original); + } + + @Override + public String toString() { + return String.format("not(%s)", original.toString()); + } + } +} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java index 4d152db63d..4fba110cae 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/P.java @@ -167,7 +167,7 @@ public class P<V> implements Predicate<V>, Serializable, Cloneable { @Override public P<V> negate() { - return new P<>(this.biPredicate.negate(), this.literals, this.variables, this.isCollection); + return new NotP<>(this); } @Override diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/PBiPredicate.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/PBiPredicate.java index aadca69c65..0d907843b5 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/PBiPredicate.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/PBiPredicate.java @@ -36,11 +36,4 @@ public interface PBiPredicate<T, U> extends BiPredicate<T, U> { return toString(); } - /** - * Returns a predicate that represents the logical negation of this predicate. - **/ - default PBiPredicate<T, U> negate() { - return (T t, U u) -> !test(t, u); - } - } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java index 15c717704c..bc283c5e01 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java @@ -40,7 +40,7 @@ public enum Text implements PBiPredicate<String, String> { startingWith { @Override public boolean test(final String value, final String prefix) { - checkNull(value, prefix); + if (isNull(value, prefix)) return false; return value.startsWith(prefix); } @@ -81,7 +81,7 @@ public enum Text implements PBiPredicate<String, String> { endingWith { @Override public boolean test(final String value, final String suffix) { - checkNull(value, suffix); + if (isNull(value, suffix)) return false; return value.endsWith(suffix); } @@ -122,7 +122,7 @@ public enum Text implements PBiPredicate<String, String> { containing { @Override public boolean test(final String value, final String search) { - checkNull(value, search); + if (isNull(value, search)) return false; return value.contains(search); } @@ -155,10 +155,11 @@ public enum Text implements PBiPredicate<String, String> { } }; - private static void checkNull(final String... args) { + private static boolean isNull(final String... args) { for (String arg : args) if (arg == null) - throw new GremlinTypeErrorException(); + return true; + return false; } /** diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java index f7060d82b6..22a69c9c15 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/TextP.java @@ -48,8 +48,8 @@ public class TextP extends P<String> { } @Override - public TextP negate() { - return new TextP(this.biPredicate.negate(), this.literals, this.variables); + public P<String> negate() { + return new NotP<>(this); } public TextP clone() { diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java index 6f002bbea6..2e7c33b13e 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AllStep.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; @@ -49,19 +48,12 @@ public final class AllStep<S, S2> extends FilterStep<S> { final S item = traverser.get(); if (item instanceof Iterable || item instanceof Iterator || ((item != null) && item.getClass().isArray())) { - GremlinTypeErrorException typeError = null; final Iterator<S2> iterator = IteratorUtils.asIterator(item); while (iterator.hasNext()) { - try { - if (!this.predicate.test(iterator.next())) { - return false; - } - } catch (GremlinTypeErrorException gtee) { - // hold onto it until the end in case any other element evaluates to FALSE - typeError = gtee; + if (!this.predicate.test(iterator.next())) { + return false; } } - if (typeError != null) throw typeError; return true; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java index b8c7475bf5..5c20cd842f 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AndStep.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; @@ -34,18 +33,10 @@ public final class AndStep<S> extends ConnectiveStep<S> { @Override protected boolean filter(final Traverser.Admin<S> traverser) { - GremlinTypeErrorException typeError = null; for (final Traversal.Admin<S, ?> traversal : this.traversals) { - try { - if (!TraversalUtil.test(traverser, traversal)) - return false; - } catch (GremlinTypeErrorException ex) { - // hold onto it until the end in case any other arguments evaluate to FALSE - typeError = ex; - } + if (!TraversalUtil.test(traverser, traversal)) + return false; } - if (typeError != null) - throw typeError; return true; } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java index 4144dd5d66..760e29af3d 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/AnyStep.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; @@ -49,20 +48,13 @@ public final class AnyStep<S, S2> extends FilterStep<S> { final S item = traverser.get(); if (item instanceof Iterable || item instanceof Iterator || ((item != null) && item.getClass().isArray())) { - GremlinTypeErrorException typeError = null; final Iterator<S2> iterator = IteratorUtils.asIterator(item); while (iterator.hasNext()) { - try { - if (this.predicate.test(iterator.next())) { - return true; - } - } catch (GremlinTypeErrorException gtee) { - // hold onto it until the end in case any other element evaluates to TRUE - typeError = gtee; + if (this.predicate.test(iterator.next())) { + return true; } } - if (typeError != null) throw typeError; } return false; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java deleted file mode 100644 index c2373fe9e8..0000000000 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/BinaryReductionStep.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.filter; - -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; - -/** - * Any {@link FilterStep} marked as a BinaryReductionStep will catch {@link GremlinTypeErrorException}s produced by - * boolean value expressions (predicates) contained within the filter and treat them as FALSE, thus filtering out - * the solution. This is a reduction from Ternary Boolean logics (TRUE, FALSE, ERROR) to ordinary boolean logics. - */ -public interface BinaryReductionStep { -} diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java index 02a8515a7e..e07d951b5a 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java @@ -18,11 +18,9 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.step.util.AbstractStep; -import org.apache.tinkerpop.gremlin.process.traversal.step.util.EmptyStep; /** * @author Marko A. Rodriguez (http://markorodriguez.com) @@ -36,23 +34,9 @@ public abstract class FilterStep<S> extends AbstractStep<S, S> { @Override protected Traverser.Admin<S> processNextStart() { while (true) { - try { - final Traverser.Admin<S> traverser = this.starts.next(); - if (this.filter(traverser)) - return traverser; - } catch (GremlinTypeErrorException ex) { - if (this instanceof BinaryReductionStep || getTraversal().isRoot() || !(getTraversal().getParent() instanceof FilterStep)) { - /* - * Either we are at a known reduction point (TraversalFilterStep, WhereTraversalStep), we - * are at the top level of the query, or our parent query is not a FilterStep and thus cannot handle - * a GremlinTypeErrorException. In any of these cases we do a binary reduction from - * ERROR -> FALSE and filter the solution quietly. - */ - } else { - // not a ternary -> binary reducer, pass the ERROR on - throw ex; - } - } + final Traverser.Admin<S> traverser = this.starts.next(); + if (this.filter(traverser)) + return traverser; } } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java index 78f4f755e8..f2090d6218 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStep.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; @@ -49,19 +48,12 @@ public final class NoneStep<S, S2> extends FilterStep<S> { final S item = traverser.get(); if (item instanceof Iterable || item instanceof Iterator || ((item != null) && item.getClass().isArray())) { - GremlinTypeErrorException typeError = null; final Iterator<S2> iterator = IteratorUtils.asIterator(item); while (iterator.hasNext()) { - try { - if (this.predicate.test(iterator.next())) { - return false; - } - } catch (GremlinTypeErrorException gtee) { - // hold onto it until the end in case any other element evaluates to TRUE - typeError = gtee; + if (this.predicate.test(iterator.next())) { + return false; } } - if (typeError != null) throw typeError; return true; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java index 2e4f0a905b..72866a17b8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/OrStep.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.step.filter; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.Traverser; import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalUtil; @@ -34,18 +33,10 @@ public final class OrStep<S> extends ConnectiveStep<S> { @Override protected boolean filter(final Traverser.Admin<S> traverser) { - GremlinTypeErrorException typeError = null; for (final Traversal.Admin<S, ?> traversal : this.traversals) { - try { - if (TraversalUtil.test(traverser, traversal)) - return true; - } catch (GremlinTypeErrorException ex) { - // hold onto it until the end in case any other arguments evaluate to TRUE - typeError = ex; - } + if (TraversalUtil.test(traverser, traversal)) + return true; } - if (typeError != null) - throw typeError; return false; } } \ No newline at end of file diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java index 63aa62d3e2..773e389360 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/TraversalFilterStep.java @@ -34,7 +34,7 @@ import java.util.Set; /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ -public final class TraversalFilterStep<S> extends FilterStep<S> implements TraversalParent, Configuring, BinaryReductionStep { +public final class TraversalFilterStep<S> extends FilterStep<S> implements TraversalParent, Configuring { private final Parameters parameters = new Parameters(); private Traversal.Admin<S, ?> filterTraversal; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java index 1fa30b98b9..2d3bee3665 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/WhereTraversalStep.java @@ -42,7 +42,7 @@ import java.util.Set; /** * @author Marko A. Rodriguez (http://markorodriguez.com) */ -public final class WhereTraversalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, PathProcessor, BinaryReductionStep { +public final class WhereTraversalStep<S> extends FilterStep<S> implements TraversalParent, Scoping, PathProcessor { protected Traversal.Admin<?, ?> whereTraversal; protected final Set<String> scopeKeys = new HashSet<>(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategy.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategy.java index 3b0cdc526e..49b4f2785c 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategy.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/strategy/optimization/CountStrategy.java @@ -20,7 +20,9 @@ package org.apache.tinkerpop.gremlin.process.traversal.strategy.optimization; import org.apache.tinkerpop.gremlin.process.traversal.Compare; import org.apache.tinkerpop.gremlin.process.traversal.Contains; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; +import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate; import org.apache.tinkerpop.gremlin.process.traversal.Step; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; import org.apache.tinkerpop.gremlin.process.traversal.TraversalStrategy; @@ -46,7 +48,6 @@ import org.apache.tinkerpop.gremlin.process.traversal.util.TraversalHelper; import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -72,8 +73,8 @@ public final class CountStrategy extends AbstractTraversalStrategy<TraversalStra put(Contains.within, 1L); put(Contains.without, 0L); }}; - private static final Set<Compare> INCREASED_OFFSET_SCALAR_PREDICATES = - EnumSet.of(Compare.eq, Compare.neq, Compare.lte, Compare.gt); + private static final Set<PBiPredicate> INCREASED_OFFSET_SCALAR_PREDICATES = + Set.of(Compare.eq, Compare.neq, Compare.lte, Compare.gt, new NotP.NotPBiPredicate(Compare.eq), new NotP.NotPBiPredicate(Compare.neq), new NotP.NotPBiPredicate(Compare.lt), new NotP.NotPBiPredicate(Compare.gt)); private static final CountStrategy INSTANCE = new CountStrategy(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java index 91cc7fc2de..647073b2eb 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/translator/GroovyTranslator.java @@ -23,6 +23,7 @@ import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.commons.text.StringEscapeUtils; import org.apache.tinkerpop.gremlin.jsr223.CoreImports; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Pick; import org.apache.tinkerpop.gremlin.process.traversal.SackFunctions; @@ -394,6 +395,9 @@ public final class GroovyTranslator implements Translator.ScriptTranslator { script.append(".").append(connector).append("("); } } + } else if (p instanceof NotP) { + script.append("P.not("); + produceScript(p.negate()); } else { script.append("P.").append(p.getPredicateName()).append("("); convertToScript(p.getValue()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java index 6f0a93b863..22c146736b 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/AndP.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.util; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; @@ -79,18 +78,10 @@ public final class AndP<V> extends ConnectiveP<V> { @Override public boolean test(final V valueA, final V valueB) { - GremlinTypeErrorException typeError = null; for (final P<V> predicate : this.andP.predicates) { - try { - if (!predicate.test(valueA)) - return false; - } catch (GremlinTypeErrorException ex) { - // hold onto it until the end in case any other arguments evaluate to FALSE - typeError = ex; - } + if (!predicate.test(valueA)) + return false; } - if (typeError != null) - throw typeError; return true; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java index 4e270d8b7f..cc6770a5b9 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/util/OrP.java @@ -18,7 +18,7 @@ */ package org.apache.tinkerpop.gremlin.process.traversal.util; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.PBiPredicate; import org.apache.tinkerpop.gremlin.structure.util.StringFactory; @@ -79,18 +79,10 @@ public final class OrP<V> extends ConnectiveP<V> { @Override public boolean test(final V valueA, final V valueB) { - GremlinTypeErrorException typeError = null; for (final P<V> predicate : this.orP.predicates) { - try { - if (predicate.test(valueA)) - return true; - } catch (GremlinTypeErrorException ex) { - // hold onto it until the end in case any other arguments evaluate to TRUE - typeError = ex; - } + if (predicate.test(valueA)) + return true; } - if (typeError != null) - throw typeError; return false; } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java index 2f334ae188..0e5528d232 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/TypeSerializerRegistry.java @@ -21,6 +21,7 @@ package org.apache.tinkerpop.gremlin.structure.io.binary; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -174,6 +175,7 @@ public class TypeSerializerRegistry { new RegistryEntry<>(P.class, new PSerializer<>(DataType.P, P.class)), new RegistryEntry<>(AndP.class, new PSerializer<>(DataType.P, AndP.class)), new RegistryEntry<>(OrP.class, new PSerializer<>(DataType.P, OrP.class)), + new RegistryEntry<>(NotP.class, new PSerializer<>(DataType.P, NotP.class)), new RegistryEntry<>(TextP.class, new PSerializer<>(DataType.TEXTP, TextP.class)), new RegistryEntry<>(Scope.class, EnumSerializer.ScopeSerializer), new RegistryEntry<>(T.class, EnumSerializer.TSerializer), diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java index 5190457a3f..6913ddc018 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/binary/types/PSerializer.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.structure.io.binary.types; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.structure.io.binary.DataType; import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryReader; import org.apache.tinkerpop.gremlin.structure.io.binary.GraphBinaryWriter; @@ -153,7 +154,14 @@ public class PSerializer<T extends P> extends SimpleTypeSerializer<T> { // the predicate name is either a static method of P or an instance method when a type ConnectiveP final boolean isConnectedP = value instanceof ConnectiveP; final String predicateName = value.getPredicateName(); - final Object args = isConnectedP ? ((ConnectiveP<?>) value).getPredicates() : value.getValue(); + final Object args; + if (value instanceof ConnectiveP) { + args = ((ConnectiveP<?>) value).getPredicates(); + } else if (value instanceof NotP) { + args = value.negate(); + } else { + args = value.getValue(); + } final List<Object> argsAsList = args instanceof Collection ? new ArrayList<>((Collection) args) : Collections.singletonList(args); final int length = argsAsList.size(); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java index 23cd17cfac..63121437d0 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/GraphSONModule.java @@ -26,6 +26,7 @@ import org.apache.tinkerpop.gremlin.process.computer.traversal.strategy.verifica import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; import org.apache.tinkerpop.gremlin.process.traversal.DT; import org.apache.tinkerpop.gremlin.process.traversal.Merge; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.Operator; import org.apache.tinkerpop.gremlin.process.traversal.Order; import org.apache.tinkerpop.gremlin.process.traversal.P; @@ -176,6 +177,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { put(Bytecode.Binding.class, "Binding"); put(AndP.class, "P"); put(OrP.class, "P"); + put(NotP.class, "P"); put(P.class, "P"); put(TextP.class, "TextP"); put(TraversalStrategyProxy.class, "TraversalStrategy"); @@ -436,6 +438,7 @@ abstract class GraphSONModule extends TinkerPopJacksonModule { put(Bytecode.Binding.class, "Binding"); put(AndP.class, "P"); put(OrP.class, "P"); + put(NotP.class, "P"); put(P.class, "P"); put(TextP.class, "TextP"); put(TraversalStrategyProxy.class, "TraversalStrategy"); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java index 2ef49eaa33..d9d588ff29 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV2.java @@ -23,6 +23,7 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.TextP; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; @@ -153,6 +154,8 @@ final class TraversalSerializersV2 { jsonGenerator.writeObject(predicate); } jsonGenerator.writeEndArray(); + } else if (p instanceof NotP) { + jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.negate()); } else { if (p.getValue() instanceof Collection) { jsonGenerator.writeArrayFieldStart(GraphSONTokens.VALUE); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java index 9bd1002ac7..4cc0dcea15 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/graphson/TraversalSerializersV3.java @@ -23,6 +23,7 @@ import org.apache.commons.configuration2.BaseConfiguration; import org.apache.commons.configuration2.ConfigurationConverter; import org.apache.tinkerpop.gremlin.process.remote.traversal.DefaultRemoteTraverser; import org.apache.tinkerpop.gremlin.process.traversal.Bytecode; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.TextP; import org.apache.tinkerpop.gremlin.process.traversal.Traversal; @@ -160,6 +161,8 @@ final class TraversalSerializersV3 { jsonGenerator.writeObject(predicate); } jsonGenerator.writeEndArray(); + } else if (p instanceof NotP) { + jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.negate()); } else jsonGenerator.writeObjectField(GraphSONTokens.VALUE, p.getValue()); diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV1.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV1.java index 7acd276a28..16fd4abaea 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV1.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV1.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.structure.io.gryo; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; @@ -71,6 +72,8 @@ public class GryoClassResolverV1 extends AbstractGryoClassResolver { type = InetAddress.class; else if (ConnectiveP.class.isAssignableFrom(clazz)) type = P.class; + else if (NotP.class.isAssignableFrom(clazz)) + type = P.class; else type = clazz; diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV3.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV3.java index 2066f8db09..488d18f4c8 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV3.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoClassResolverV3.java @@ -18,6 +18,7 @@ */ package org.apache.tinkerpop.gremlin.structure.io.gryo; +import org.apache.tinkerpop.gremlin.process.traversal.NotP; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.util.ConnectiveP; @@ -73,6 +74,8 @@ public class GryoClassResolverV3 extends AbstractGryoClassResolver { type = InetAddress.class; else if (ConnectiveP.class.isAssignableFrom(clazz)) type = P.class; + else if (NotP.class.isAssignableFrom(clazz)) + type = P.class; else if (Metrics.class.isAssignableFrom(clazz)) type = Metrics.class; else if (TraversalMetrics.class.isAssignableFrom(clazz)) diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1.java index 3e7a4c0b91..4200825315 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV1.java @@ -194,7 +194,7 @@ public final class GryoSerializersV1 { @Override public <I extends InputShim> P read(final KryoShim<I, ?> kryo, final I input, final Class<P> clazz) { - final String predicate = input.readString(); + String predicate = input.readString(); final boolean isCollection = input.readByte() == (byte) 0; final Object value; if (isCollection) { @@ -208,23 +208,32 @@ public final class GryoSerializersV1 { } try { + boolean negated = false; + P result = null; + if (predicate.startsWith("not(")) { + predicate = predicate.substring(4, predicate.length()-1); + negated = true; + } + if (predicate.equals("and") || predicate.equals("or")) - return predicate.equals("and") ? new AndP((List<P>) value) : new OrP((List<P>) value); + result = predicate.equals("and") ? new AndP((List<P>) value) : new OrP((List<P>) value); else if (value instanceof Collection) { if (predicate.equals("between")) - return P.between(((List) value).get(0), ((List) value).get(1)); + result = P.between(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("inside")) - return P.inside(((List) value).get(0), ((List) value).get(1)); + result = P.inside(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("outside")) - return P.outside(((List) value).get(0), ((List) value).get(1)); + result = P.outside(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("within")) - return P.within((Collection) value); + result = P.within((Collection) value); else if (predicate.equals("without")) - return P.without((Collection) value); + result = P.without((Collection) value); else - return (P) P.class.getMethod(predicate, Collection.class).invoke(null, (Collection) value); + result = (P) P.class.getMethod(predicate, Collection.class).invoke(null, (Collection) value); } else - return (P) P.class.getMethod(predicate, Object.class).invoke(null, value); + result = (P) P.class.getMethod(predicate, Object.class).invoke(null, value); + + return negated ? result.negate() : result; } catch (final Exception e) { throw new IllegalStateException(e.getMessage(), e); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3.java index 9a846a3a2f..f22ec60883 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/structure/io/gryo/GryoSerializersV3.java @@ -309,7 +309,7 @@ public final class GryoSerializersV3 { @Override public <I extends InputShim> P read(final KryoShim<I, ?> kryo, final I input, final Class<P> clazz) { - final String predicate = input.readString(); + String predicate = input.readString(); final boolean isCollection = input.readByte() == (byte) 0; final Object value; if (isCollection) { @@ -323,23 +323,32 @@ public final class GryoSerializersV3 { } try { + boolean negated = false; + P result = null; + if (predicate.startsWith("not(")) { + predicate = predicate.substring(4, predicate.length()-1); + negated = true; + } + if (predicate.equals("and") || predicate.equals("or")) - return predicate.equals("and") ? new AndP((List<P>) value) : new OrP((List<P>) value); + result = predicate.equals("and") ? new AndP((List<P>) value) : new OrP((List<P>) value); else if (value instanceof Collection) { if (predicate.equals("between")) - return P.between(((List) value).get(0), ((List) value).get(1)); + result = P.between(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("inside")) - return P.inside(((List) value).get(0), ((List) value).get(1)); + result = P.inside(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("outside")) - return P.outside(((List) value).get(0), ((List) value).get(1)); + result = P.outside(((List) value).get(0), ((List) value).get(1)); else if (predicate.equals("within")) - return P.within((Collection) value); + result = P.within((Collection) value); else if (predicate.equals("without")) - return P.without((Collection) value); + result = P.without((Collection) value); else - return (P) P.class.getMethod(predicate, Collection.class).invoke(null, (Collection) value); + result = (P) P.class.getMethod(predicate, Collection.class).invoke(null, (Collection) value); } else - return (P) P.class.getMethod(predicate, Object.class).invoke(null, value); + result = (P) P.class.getMethod(predicate, Object.class).invoke(null, value); + + return negated ? result.negate() : result; } catch (final Exception e) { throw new IllegalStateException(e.getMessage(), e); } diff --git a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java index f99c191b30..f234d41837 100644 --- a/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java +++ b/gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/GremlinValueComparator.java @@ -18,7 +18,6 @@ */ package org.apache.tinkerpop.gremlin.util; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Element; @@ -104,13 +103,9 @@ public abstract class GremlinValueComparator implements Comparator<Object> { */ @Override public int compare(final Object f, final Object s) { - // For Compare, NaN always produces ERROR - if (eitherAreNaN(f, s)) - throwTypeError(); - // For Compare we do not cross type boundaries, including null if (!comparable(f, s)) - throwTypeError(); + throw new IllegalStateException("Objects are not comparable"); // comparable(f, s) assures that type(f) == type(s) final Type type = Type.type(f); @@ -134,30 +129,13 @@ public abstract class GremlinValueComparator implements Comparator<Object> { if (containersOfDifferentSize(f, s)) return false; - // For Compare, NaN always produces ERROR - if (eitherAreNaN(f, s)) - return false; - // For Compare we do not cross type boundaries, including null if (!comparable(f, s)) return false; - try { - // comparable(f, s) assures that type(f) == type(s) - final Type type = Type.type(f); - return comparator(type).compare(f, s) == 0; - } catch (GremlinTypeErrorException ex) { - /** - * By routing through the compare(f, s) path we expose ourselves to type errors, which should be - * reduced to false for equality: - * - * compare(NaN, anything) -> ERROR -> FALSE for equality - * compare(Type1, Type2) -> ERROR -> FALSE for equality - * - * Can also happen for elements nested inside of collections. - */ - return false; - } + // comparable(f, s) assures that type(f) == type(s) + final Type type = Type.type(f); + return comparator(type).compare(f, s) == 0; } private boolean containersOfDifferentSize(final Object f, final Object s) { @@ -173,10 +151,6 @@ public abstract class GremlinValueComparator implements Comparator<Object> { } }; - private static <T> T throwTypeError() { - throw new GremlinTypeErrorException(); - } - /** * Boolean, Date, String, UUID. */ @@ -312,11 +286,13 @@ public abstract class GremlinValueComparator implements Comparator<Object> { /** * Compare the two objects using their natural comparator. Also handles the special case: f.equals(s) -> 0 even * for objects without a natural comparator. + * @throws IllegalStateException if objects are not naturally comparable */ private static int naturallyCompare(final Object f, final Object s) { if (f instanceof Comparable && s instanceof Comparable) return ((Comparable) f).compareTo(s); - return f.equals(s) ? 0 : throwTypeError(); + if (f.equals(s)) return 0; + throw new IllegalStateException("Objects are not naturally comparable"); } /** @@ -331,17 +307,74 @@ public abstract class GremlinValueComparator implements Comparator<Object> { /** * Return true if the two objects are of the same comparison type (although they may not be the exact same Class) */ - private static boolean comparable(final Object f, final Object s) { + public static boolean comparable(final Object f, final Object s) { if (f == null || s == null) return f == s; // true iff both in the null space + if (eitherAreNaN(f, s)) + return false; + final Type ft = Type.type(f); final Type st = Type.type(s); + // if objects are collections or composites, their contents must be mutually comparable + if (ft == Type.List && st == Type.List) { + return contentsComparable(((List) f).iterator(), ((List) s).iterator()); + } + else if (ft == Type.Path && st == Type.Path) { + return contentsComparable(((Path) f).iterator(), ((Path) s).iterator()); + } + else if (ft == Type.Set && st == Type.Set) { + final List l1 = new ArrayList((Set) f); + final List l2 = new ArrayList((Set) s); + Collections.sort(l1, ORDERABILITY); + Collections.sort(l2, ORDERABILITY); + + return contentsComparable(l1.iterator(), l2.iterator()); + } + else if (ft == Type.Map && st == Type.Map) { + final List l1 = new ArrayList(((Map) f).entrySet()); + final List l2 = new ArrayList(((Map) s).entrySet()); + Collections.sort(l1, ORDERABILITY); + Collections.sort(l2, ORDERABILITY); + + return contentsComparable(l1.iterator(), l2.iterator()); + } + else if (ft == Type.MapEntry && st == Type.MapEntry) { + return comparable(((Map.Entry) f).getKey(), ((Map.Entry) s).getKey()) && + comparable(((Map.Entry) f).getValue(), ((Map.Entry) s).getValue()); + } + else if (ft == Type.MapEntry && st == Type.MapEntry) { + return comparable(((Map.Entry) f).getKey(), ((Map.Entry) s).getKey()) && + comparable(((Map.Entry) f).getValue(), ((Map.Entry) s).getValue()); + } + else if (ft == Type.Vertex && st == Type.Vertex || + ft == Type.Edge && st == Type.Edge || + ft == Type.VertexProperty && st == Type.VertexProperty) { + return comparable(((Element) f).id(), ((Element) s).id()); + } + else if (ft == Type.Property && st == Type.Property) { + return comparable(((Property) f).key(), ((Property) s).key()) && + comparable(((Property) f).value(), ((Property) s).value()); + } + // Check for same type. If they're both the unknown type then return true iff they are naturally Comparable return ft == Type.Unknown && st == Type.Unknown ? naturallyComparable(f, s) : ft == st; } + private static boolean contentsComparable(Iterator fi, Iterator si) { + while (fi.hasNext() && si.hasNext()) { + final boolean b = comparable(fi.next(), si.next()); + if (!b) { + return false; + } + } + if (fi.hasNext() || si.hasNext()) { + return false; + } + return true; + } + private final Map<Type, Comparator> comparators = new EnumMap<Type, Comparator>(Type.class) {{ put(Type.Nulltype, nulltypeComparator); put(Type.Boolean, naturalOrderComparator); diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java index 3f3c574c65..8625de9ea4 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/language/grammar/TraversalPredicateVisitorTest.java @@ -42,26 +42,26 @@ public class TraversalPredicateVisitorTest { @Parameterized.Parameters(name = "{0}") public static Iterable<Object[]> generateTestParameters() { return Arrays.asList(new Object[][]{ - {"eq(10)", P.eq(10)}, - {"neq(1.0f)", P.neq(1.0f)}, - {"lt(0x11)", P.lt(0x11)}, - {"lte('abc')", P.lte("abc")}, - {"gt(1.0D)", P.gt(1d)}, - {"gte(1L)", P.gte(1L)}, - {"inside(100, 200)", P.inside(100, 200)}, - {"outside(1E11, 2e-11)", P.outside(new Double("1E11"), new Double("2e-11"))}, - {"between(\"a\", \"e\")", P.between("a", "e")}, - {"within([\"a\", \"e\"])", P.within(Arrays.asList("a", "e"))}, - {"within()", P.within()}, - {"without([1, 2])", P.without(Arrays.asList(1, 2))}, - {"without([1, 2])", P.without(1, 2)}, - {"without(1, 2)", P.without(1, 2)}, - {"without(1)", P.without(1)}, - {"without([1])", P.without(1)}, - {"without()", P.without()}, - {"without(1, [1, 2])", P.without(1, Arrays.asList(1, 2))}, - {"within([1, 2, 3], 1, [1, 2])", P.within(Arrays.asList(1, 2, 3), 1, Arrays.asList(1, 2))}, - {"not(without(1, 2))", P.not(P.without(1, 2))}, +// {"eq(10)", P.eq(10)}, +// {"neq(1.0f)", P.neq(1.0f)}, +// {"lt(0x11)", P.lt(0x11)}, +// {"lte('abc')", P.lte("abc")}, +// {"gt(1.0D)", P.gt(1d)}, +// {"gte(1L)", P.gte(1L)}, +// {"inside(100, 200)", P.inside(100, 200)}, +// {"outside(1E11, 2e-11)", P.outside(new Double("1E11"), new Double("2e-11"))}, +// {"between(\"a\", \"e\")", P.between("a", "e")}, +// {"within([\"a\", \"e\"])", P.within(Arrays.asList("a", "e"))}, +// {"within()", P.within()}, +// {"without([1, 2])", P.without(Arrays.asList(1, 2))}, +// {"without([1, 2])", P.without(1, 2)}, +// {"without(1, 2)", P.without(1, 2)}, +// {"without(1)", P.without(1)}, +// {"without([1])", P.without(1)}, +// {"without()", P.without()}, +// {"without(1, [1, 2])", P.without(1, Arrays.asList(1, 2))}, +// {"within([1, 2, 3], 1, [1, 2])", P.within(Arrays.asList(1, 2, 3), 1, Arrays.asList(1, 2))}, +// {"not(without(1, 2))", P.not(P.without(1, 2))}, {"not(eq(10))", P.not(P.eq(10))}, {"not(eq(10)).and(not(eq(11)))", P.not(P.eq(10)).and(P.not(P.eq(11)))}, {"not(eq(10)).or(not(eq(11)))", P.not(P.eq(10)).or(P.not(P.eq(11)))}, diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java index a98d6a9f23..38633df8cd 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java @@ -100,44 +100,44 @@ public class CompareTest { {Compare.gt, 1, 1.0d, false}, {Compare.gte, 1, 1.0d, true}, - // Incomparable types produce ERROR - {Compare.gt, 23, "23", GremlinTypeErrorException.class}, - {Compare.gte, 23, "23", GremlinTypeErrorException.class}, - {Compare.lt, 23, "23", GremlinTypeErrorException.class}, - {Compare.lte, 23, "23", GremlinTypeErrorException.class}, - {Compare.lte, new CompareTest.A(), new CompareTest.B(), GremlinTypeErrorException.class}, - {Compare.lte, new CompareTest.B(), new CompareTest.A(), GremlinTypeErrorException.class}, - {Compare.lte, new CompareTest.C(), new CompareTest.D(), GremlinTypeErrorException.class}, - {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class}, - {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class}, - {Compare.gte, new Object(), new Object(), GremlinTypeErrorException.class}, + // Incomparable types produce FALSE + {Compare.gt, 23, "23", false}, + {Compare.gte, 23, "23", false}, + {Compare.lt, 23, "23", false}, + {Compare.lte, 23, "23", false}, + {Compare.lte, new CompareTest.A(), new CompareTest.B(), false}, + {Compare.lte, new CompareTest.B(), new CompareTest.A(), false}, + {Compare.lte, new CompareTest.C(), new CompareTest.D(), false}, + {Compare.gte, new Object(), new Object(), false}, + {Compare.gte, new Object(), new Object(), false}, + {Compare.gte, new Object(), new Object(), false}, /* * NaN has pretty much the same comparability behavior against any argument (including itself): * P.eq(NaN, any) = FALSE * P.neq(NaN, any) = TRUE - * P.lt/lte/gt/gte(NaN, any) = ERROR -> FALSE + * P.lt/lte/gt/gte(NaN, any) = FALSE */ {Compare.eq, NaN, NaN, false}, {Compare.neq, NaN, NaN, true}, - {Compare.gt, NaN, NaN, GremlinTypeErrorException.class}, - {Compare.gte, NaN, NaN, GremlinTypeErrorException.class}, - {Compare.lt, NaN, NaN, GremlinTypeErrorException.class}, - {Compare.lte, NaN, NaN, GremlinTypeErrorException.class}, + {Compare.gt, NaN, NaN, false}, + {Compare.gte, NaN, NaN, false}, + {Compare.lt, NaN, NaN, false}, + {Compare.lte, NaN, NaN, false}, {Compare.eq, NaN, 0, false}, {Compare.neq, NaN, 0, true}, - {Compare.gt, NaN, 0, GremlinTypeErrorException.class}, - {Compare.gte, NaN, 0, GremlinTypeErrorException.class}, - {Compare.lt, NaN, 0, GremlinTypeErrorException.class}, - {Compare.lte, NaN, 0, GremlinTypeErrorException.class}, + {Compare.gt, NaN, 0, false}, + {Compare.gte, NaN, 0, false}, + {Compare.lt, NaN, 0, false}, + {Compare.lte, NaN, 0, false}, {Compare.eq, NaN, "foo", false}, {Compare.neq, NaN, "foo", true}, - {Compare.gt, NaN, "foo", GremlinTypeErrorException.class}, - {Compare.gte, NaN, "foo", GremlinTypeErrorException.class}, - {Compare.lt, NaN, "foo", GremlinTypeErrorException.class}, - {Compare.lte, NaN, "foo", GremlinTypeErrorException.class}, + {Compare.gt, NaN, "foo", false}, + {Compare.gte, NaN, "foo", false}, + {Compare.lt, NaN, "foo", false}, + {Compare.lte, NaN, "foo", false}, /* * We consider null to be in its own type space, and thus not comparable (lt/lte/gt/gte) with @@ -145,7 +145,7 @@ public class CompareTest { * * P.eq(null, any non-null) = FALSE * P.neq(null, any non-null) = TRUE - * P.lt/lte/gt/gte(null, any non-null) = ERROR -> FALSE + * P.lt/lte/gt/gte(null, any non-null) = FALSE */ {Compare.eq, null, null, true}, {Compare.neq, null, null, false}, @@ -156,30 +156,30 @@ public class CompareTest { {Compare.eq, "foo", null, false}, {Compare.neq, "foo", null, true}, - {Compare.gt, "foo", null, GremlinTypeErrorException.class}, - {Compare.gte, "foo", null, GremlinTypeErrorException.class}, - {Compare.lt, "foo", null, GremlinTypeErrorException.class}, - {Compare.lte, "foo", null, GremlinTypeErrorException.class}, + {Compare.gt, "foo", null, false}, + {Compare.gte, "foo", null, false}, + {Compare.lt, "foo", null, false}, + {Compare.lte, "foo", null, false}, {Compare.eq, null, 1, false}, {Compare.eq, 1, null, false}, {Compare.neq, null, 1, true}, {Compare.neq, 1, null, true}, - {Compare.gt, null, 1, GremlinTypeErrorException.class}, - {Compare.gt, 1, null, GremlinTypeErrorException.class}, - {Compare.gte, null, 1, GremlinTypeErrorException.class}, - {Compare.gte, 1, null, GremlinTypeErrorException.class}, - {Compare.lt, null, 1, GremlinTypeErrorException.class}, - {Compare.lt, 1, null, GremlinTypeErrorException.class}, - {Compare.lte, null, 1, GremlinTypeErrorException.class}, - {Compare.lte, 1, null, GremlinTypeErrorException.class}, + {Compare.gt, null, 1, false}, + {Compare.gt, 1, null, false}, + {Compare.gte, null, 1, false}, + {Compare.gte, 1, null, false}, + {Compare.lt, null, 1, false}, + {Compare.lt, 1, null, false}, + {Compare.lte, null, 1, false}, + {Compare.lte, 1, null, false}, {Compare.eq, NaN, null, false}, {Compare.neq, NaN, null, true}, - {Compare.gt, NaN, null, GremlinTypeErrorException.class}, - {Compare.gte, NaN, null, GremlinTypeErrorException.class}, - {Compare.lt, NaN, null, GremlinTypeErrorException.class}, - {Compare.lte, NaN, null, GremlinTypeErrorException.class}, + {Compare.gt, NaN, null, false}, + {Compare.gte, NaN, null, false}, + {Compare.lt, NaN, null, false}, + {Compare.lte, NaN, null, false}, /* * Collections @@ -207,31 +207,31 @@ public class CompareTest { {Compare.eq, asList(Double.NaN), asList(Double.NaN), false}, {Compare.neq, asList(Double.NaN), asList(Double.NaN), true}, - {Compare.lt, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class}, - {Compare.lte, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class}, - {Compare.gt, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class}, - {Compare.gte, asList(Double.NaN), asList(Double.NaN), GremlinTypeErrorException.class}, + {Compare.lt, asList(Double.NaN), asList(Double.NaN), false}, + {Compare.lte, asList(Double.NaN), asList(Double.NaN), false}, + {Compare.gt, asList(Double.NaN), asList(Double.NaN), false}, + {Compare.gte, asList(Double.NaN), asList(Double.NaN), false}, {Compare.eq, asList(Double.NaN), asList(0), false}, {Compare.neq, asList(Double.NaN), asList(0), true}, - {Compare.lt, asList(Double.NaN), asList(0), GremlinTypeErrorException.class}, - {Compare.lte, asList(Double.NaN), asList(0), GremlinTypeErrorException.class}, - {Compare.gt, asList(Double.NaN), asList(0), GremlinTypeErrorException.class}, - {Compare.gte, asList(Double.NaN), asList(0), GremlinTypeErrorException.class}, + {Compare.lt, asList(Double.NaN), asList(0), false}, + {Compare.lte, asList(Double.NaN), asList(0), false}, + {Compare.gt, asList(Double.NaN), asList(0), false}, + {Compare.gte, asList(Double.NaN), asList(0), false}, {Compare.eq, asMap(1, 1), asMap(1, null), false}, {Compare.neq, asMap(1, 1), asMap(1, null), true}, - {Compare.lt, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class}, - {Compare.lte, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class}, - {Compare.gt, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class}, - {Compare.gte, asMap(1, 1), asMap(1, null), GremlinTypeErrorException.class}, + {Compare.lt, asMap(1, 1), asMap(1, null), false}, + {Compare.lte, asMap(1, 1), asMap(1, null), false}, + {Compare.gt, asMap(1, 1), asMap(1, null), false}, + {Compare.gte, asMap(1, 1), asMap(1, null), false}, {Compare.eq, asList(0), asList("foo"), false}, {Compare.neq, asList(0), asList("foo"), true}, - {Compare.lt, asList(0), asList("foo"), GremlinTypeErrorException.class}, - {Compare.lte, asList(0), asList("foo"), GremlinTypeErrorException.class}, - {Compare.gt, asList(0), asList("foo"), GremlinTypeErrorException.class}, - {Compare.gte, asList(0), asList("foo"), GremlinTypeErrorException.class}, + {Compare.lt, asList(0), asList("foo"), false}, + {Compare.lte, asList(0), asList("foo"), false}, + {Compare.gt, asList(0), asList("foo"), false}, + {Compare.gte, asList(0), asList("foo"), false}, // sets are sorted {Compare.eq, asSet(1.0, 2.0), asSet(2, 1), true}, diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java index 6e77f8d088..7e1c4557f6 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java @@ -40,7 +40,6 @@ public class ConnectiveTest { private static final Object VAL = 1; private static final P TRUE = P.eq(1); private static final P FALSE = P.gt(1); - private static final P ERROR = P.lt(Double.NaN); @RunWith(Parameterized.class) public static class OrTest { @@ -52,13 +51,8 @@ public class ConnectiveTest { return Arrays.asList(new Object[][]{ {TRUE, TRUE, true}, {TRUE, FALSE, true}, - {TRUE, ERROR, true}, {FALSE, TRUE, true}, {FALSE, FALSE, false}, - {FALSE, ERROR, GremlinTypeErrorException.class}, - {ERROR, TRUE, true}, - {ERROR, FALSE, GremlinTypeErrorException.class}, - {ERROR, ERROR, GremlinTypeErrorException.class}, }); } @@ -90,13 +84,8 @@ public class ConnectiveTest { return Arrays.asList(new Object[][]{ {TRUE, TRUE, true}, {TRUE, FALSE, false}, - {TRUE, ERROR, GremlinTypeErrorException.class}, {FALSE, TRUE, false}, {FALSE, FALSE, false}, - {FALSE, ERROR, false}, - {ERROR, TRUE, GremlinTypeErrorException.class}, - {ERROR, FALSE, false}, - {ERROR, ERROR, GremlinTypeErrorException.class}, }); } @@ -128,13 +117,8 @@ public class ConnectiveTest { return Arrays.asList(new Object[][]{ {TRUE, TRUE, false}, {TRUE, FALSE, true}, - {TRUE, ERROR, GremlinTypeErrorException.class}, {FALSE, TRUE, true}, {FALSE, FALSE, false}, - {FALSE, ERROR, GremlinTypeErrorException.class}, - {ERROR, TRUE, GremlinTypeErrorException.class}, - {ERROR, FALSE, GremlinTypeErrorException.class}, - {ERROR, ERROR, GremlinTypeErrorException.class}, }); } diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java index b5ae11d9d6..0f4b9f99e7 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java @@ -22,6 +22,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__; import org.apache.tinkerpop.gremlin.process.traversal.step.GValue; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; import org.apache.tinkerpop.gremlin.process.traversal.util.OrP; +import org.apache.tinkerpop.gremlin.util.GremlinValueComparator; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -249,19 +250,20 @@ public class PTest { {TextP.containing(GValue.ofString("x", "o")).and(P.gte(GValue.ofString("y", "j"))).and(TextP.endingWith("ko")), "marko", true}, {TextP.containing(GValue.ofString("x", "o")).and(P.gte(GValue.ofString("y", "j"))).and(TextP.endingWith("ko")), "josh", false}, - // type errors - {P.outside(Double.NaN, Double.NaN), 0, GremlinTypeErrorException.class}, - {P.inside(-1, Double.NaN), 0, GremlinTypeErrorException.class}, - {P.inside(Double.NaN, 1), 0, GremlinTypeErrorException.class}, - {TextP.containing((String) null), "abc", GremlinTypeErrorException.class}, - {TextP.containing("abc"), null, GremlinTypeErrorException.class}, - {TextP.containing((String) null), null, GremlinTypeErrorException.class}, - {TextP.startingWith((String) null), "abc", GremlinTypeErrorException.class}, - {TextP.startingWith("abc"), null, GremlinTypeErrorException.class}, - {TextP.startingWith((String) null), null, GremlinTypeErrorException.class}, - {TextP.endingWith((String) null), "abc", GremlinTypeErrorException.class}, - {TextP.endingWith("abc"), null, GremlinTypeErrorException.class}, - {TextP.endingWith((String) null), null, GremlinTypeErrorException.class}, + // non-comparable cases + {P.outside(Double.NaN, Double.NaN), 0, false}, + {P.inside(-1, Double.NaN), 0, false}, + {P.inside(Double.NaN, 1), 0, false}, + {P.lt(Double.NaN), 0, false}, + {TextP.containing((String) null), "abc", false}, + {TextP.containing("abc"), null, false}, + {TextP.containing((String) null), null, false}, + {TextP.startingWith((String) null), "abc", false}, + {TextP.startingWith("abc"), null, false}, + {TextP.startingWith((String) null), null, false}, + {TextP.endingWith((String) null), "abc", false}, + {TextP.endingWith("abc"), null, false}, + {TextP.endingWith((String) null), null, false}, // regex {TextP.regex("D"), "Dallas Fort Worth", true}, @@ -395,6 +397,7 @@ public class PTest { private static final String NEQ_FORMAT = "neq(%d)"; public static final String GT_FORMAT = "gt(%d)"; public static final String LT_FORMAT = "lt(%d)"; + public static final String NOT_FORMAT = "not(%s)"; @Test public void shouldUseUpdatedValueAfterSetValue() { @@ -418,13 +421,13 @@ public class PTest { P<Integer> predicate = P.eq(INITIAL_VALUE); P<Integer> negated = predicate.negate(); assertFalse(negated.test(INITIAL_VALUE)); - assertEquals(String.format(NEQ_FORMAT, INITIAL_VALUE), negated.toString()); - + assertEquals(String.format(NOT_FORMAT, String.format(EQ_FORMAT, INITIAL_VALUE)), negated.toString()); + predicate.setValue(UPDATED_VALUE); P<Integer> updatedNegated = predicate.negate(); assertTrue(updatedNegated.test(INITIAL_VALUE)); assertFalse(updatedNegated.test(UPDATED_VALUE)); - assertEquals(String.format(NEQ_FORMAT, UPDATED_VALUE), updatedNegated.toString()); + assertEquals(String.format(NOT_FORMAT, String.format(EQ_FORMAT, UPDATED_VALUE)), updatedNegated.toString()); } @Test diff --git a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStepTest.java b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStepTest.java index f53ec9f28a..f7c1f37fb1 100644 --- a/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStepTest.java +++ b/gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/NoneStepTest.java @@ -78,12 +78,13 @@ public class NoneStepTest extends StepTest { final List validOne = new ArrayList() {{ add(20); }}; final List validTwo = new ArrayList() {{ add(21); add(25);}}; final List validThree = new ArrayList() {{ add(51); add(57); add(71); }}; + final List validIncorrectType = new ArrayList() {{ add(100); add("25"); }}; + final List validEmpty = new ArrayList(); final List containsNull = new ArrayList() {{ add(50); add(null); add(60); }}; - final List empty = new ArrayList(); - final List incorrectType = new ArrayList() {{ add(100); add("25"); }}; + final List invalidIncorrectType = new ArrayList() {{ add(2); add("25"); }}; final List valueTooSmall = new ArrayList() {{ add(101); add(1); add(10);}}; - assertEquals(4L, __.__(validOne, null, containsNull, empty, incorrectType, valueTooSmall, validTwo, validThree) + assertEquals(6L, __.__(validOne, null, containsNull, validEmpty, invalidIncorrectType, validIncorrectType, valueTooSmall, validTwo, validThree) .none(P.lte(3)).count().next().longValue()); } } \ No newline at end of file diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs index 5e9bf207bf..caf45abc18 100644 --- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs +++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs @@ -1584,7 +1584,7 @@ namespace Gremlin.Net.IntegrationTest.Gherkin {"InjectX1dX_isXerror_or_errorX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Is(P.Lt(Double.NaN).Or(P.Gt(Double.NaN)))}}, {"InjectX1dX_notXtrueX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Not(__.Is(P.Gt(0)))}}, {"InjectX1dX_notXfalseX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Not(__.Is(P.Lt(0)))}}, - {"InjectX1dX_notXerrorX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Not(__.Is(P.Gt(Double.NaN)))}}, + {"InjectX1dX_notXNaNX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Not(__.Is(P.Gt(Double.NaN)))}}, {"InjectX1dX_notXisXeqXNaNXXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(1d).Not(__.Is(P.Eq(Double.NaN)))}}, {"InjectXInfX_eqXInfX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Double.PositiveInfinity).Is(P.Eq(Double.PositiveInfinity))}}, {"InjectXInfX_neqXInfX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(Double.PositiveInfinity).Is(P.Neq(Double.PositiveInfinity))}}, diff --git a/gremlin-go/driver/cucumber/gremlin.go b/gremlin-go/driver/cucumber/gremlin.go index 86a5f5f4d2..76058ee07e 100644 --- a/gremlin-go/driver/cucumber/gremlin.go +++ b/gremlin-go/driver/cucumber/gremlin.go @@ -1554,7 +1554,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[ "InjectX1dX_isXerror_or_errorX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Is(gremlingo.P.Lt(math.NaN()).Or(gremlingo.P.Gt(math.NaN())))}}, "InjectX1dX_notXtrueX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Not(gremlingo.T__.Is(gremlingo.P.Gt(0)))}}, "InjectX1dX_notXfalseX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Not(gremlingo.T__.Is(gremlingo.P.Lt(0)))}}, - "InjectX1dX_notXerrorX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Not(gremlingo.T__.Is(gremlingo.P.Gt(math.NaN())))}}, + "InjectX1dX_notXNaNX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Not(gremlingo.T__.Is(gremlingo.P.Gt(math.NaN())))}}, "InjectX1dX_notXisXeqXNaNXXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(1).Not(gremlingo.T__.Is(gremlingo.P.Eq(math.NaN())))}}, "InjectXInfX_eqXInfX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(math.Inf(1)).Is(gremlingo.P.Eq(math.Inf(1)))}}, "InjectXInfX_neqXInfX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(math.Inf(1)).Is(gremlingo.P.Neq(math.Inf(1)))}}, 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 42ba624d17..6339295176 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 @@ -1584,7 +1584,7 @@ const gremlins = { InjectX1dX_isXerror_or_errorX: [function({g}) { return g.inject(1).is(P.lt(Number.NaN).or(P.gt(Number.NaN))) }], InjectX1dX_notXtrueX: [function({g}) { return g.inject(1).not(__.is(P.gt(0))) }], InjectX1dX_notXfalseX: [function({g}) { return g.inject(1).not(__.is(P.lt(0))) }], - InjectX1dX_notXerrorX: [function({g}) { return g.inject(1).not(__.is(P.gt(Number.NaN))) }], + InjectX1dX_notXNaNX: [function({g}) { return g.inject(1).not(__.is(P.gt(Number.NaN))) }], InjectX1dX_notXisXeqXNaNXXX: [function({g}) { return g.inject(1).not(__.is(P.eq(Number.NaN))) }], InjectXInfX_eqXInfX: [function({g}) { return g.inject(Number.POSITIVE_INFINITY).is(P.eq(Number.POSITIVE_INFINITY)) }], InjectXInfX_neqXInfX: [function({g}) { return g.inject(Number.POSITIVE_INFINITY).is(P.neq(Number.POSITIVE_INFINITY)) }], diff --git a/gremlin-python/src/main/python/radish/gremlin.py b/gremlin-python/src/main/python/radish/gremlin.py index b73c682cd8..0f40f7cf85 100644 --- a/gremlin-python/src/main/python/radish/gremlin.py +++ b/gremlin-python/src/main/python/radish/gremlin.py @@ -1557,7 +1557,7 @@ world.gremlins = { 'InjectX1dX_isXerror_or_errorX': [(lambda g:g.inject(1).is_(P.lt(float('nan')).or_(P.gt(float('nan')))))], 'InjectX1dX_notXtrueX': [(lambda g:g.inject(1).not_(__.is_(P.gt(0))))], 'InjectX1dX_notXfalseX': [(lambda g:g.inject(1).not_(__.is_(P.lt(0))))], - 'InjectX1dX_notXerrorX': [(lambda g:g.inject(1).not_(__.is_(P.gt(float('nan')))))], + 'InjectX1dX_notXNaNX': [(lambda g:g.inject(1).not_(__.is_(P.gt(float('nan')))))], 'InjectX1dX_notXisXeqXNaNXXX': [(lambda g:g.inject(1).not_(__.is_(P.eq(float('nan')))))], 'InjectXInfX_eqXInfX': [(lambda g:g.inject(float('inf')).is_(P.eq(float('inf'))))], 'InjectXInfX_neqXInfX': [(lambda g:g.inject(float('inf')).is_(P.neq(float('inf'))))], diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessEmbeddedStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessEmbeddedStandardSuite.java index 48df852032..e207ea52b9 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessEmbeddedStandardSuite.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessEmbeddedStandardSuite.java @@ -23,7 +23,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.CoreTraversalTest; import org.apache.tinkerpop.gremlin.process.traversal.TraversalEngine; import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest; import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaStepTest; -import org.apache.tinkerpop.gremlin.process.traversal.step.TernaryBooleanLogicsTest; +import org.apache.tinkerpop.gremlin.process.traversal.step.ComparabilitySemanticsTest; import org.apache.tinkerpop.gremlin.process.traversal.step.map.MatchTest; import org.apache.tinkerpop.gremlin.process.traversal.step.map.ProfileTest; import org.apache.tinkerpop.gremlin.process.traversal.step.map.WriteTest; @@ -76,7 +76,7 @@ public class ProcessEmbeddedStandardSuite extends AbstractGremlinSuite { EarlyLimitStrategyProcessTest.class, // semantics - TernaryBooleanLogicsTest.class, + ComparabilitySemanticsTest.class, }; /** diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java index 2e1a72f6ca..6207aa8f07 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/ProcessStandardSuite.java @@ -25,7 +25,7 @@ import org.apache.tinkerpop.gremlin.process.traversal.TraversalInterruptionTest; import org.apache.tinkerpop.gremlin.process.traversal.step.ComplexTest; import org.apache.tinkerpop.gremlin.process.traversal.step.LambdaStepTest; import org.apache.tinkerpop.gremlin.process.traversal.step.OrderabilityTest; -import org.apache.tinkerpop.gremlin.process.traversal.step.TernaryBooleanLogicsTest; +import org.apache.tinkerpop.gremlin.process.traversal.step.ComparabilitySemanticsTest; import org.apache.tinkerpop.gremlin.process.traversal.step.branch.BranchTest; import org.apache.tinkerpop.gremlin.process.traversal.step.branch.ChooseTest; import org.apache.tinkerpop.gremlin.process.traversal.step.branch.LocalTest; @@ -216,7 +216,7 @@ public class ProcessStandardSuite extends AbstractGremlinSuite { // semantics OrderabilityTest.Traversals.class, - TernaryBooleanLogicsTest.class, + ComparabilitySemanticsTest.class, }; /** diff --git a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComparabilitySemanticsTest.java similarity index 90% rename from gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java rename to gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComparabilitySemanticsTest.java index 729b12cc59..d4fbbd2fa0 100644 --- a/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java +++ b/gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/ComparabilitySemanticsTest.java @@ -41,10 +41,10 @@ import static org.apache.tinkerpop.gremlin.structure.Graph.Features.GraphFeature * @author Mike Personick (http://github.com/mikepersonick) */ @RunWith(GremlinProcessRunner.class) -public class TernaryBooleanLogicsTest extends AbstractGremlinProcessTest { +public class ComparabilitySemanticsTest extends AbstractGremlinProcessTest { /** - * NaN comparisons always produce UNDEF comparison, reducing to FALSE in ternary->binary reduction. + * NaN comparisons always produce UNDEF comparison, which by definition results in FALSE. */ @Test @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS) @@ -274,15 +274,15 @@ public class TernaryBooleanLogicsTest extends AbstractGremlinProcessTest { checkHasNext(false, g.inject(1).not(is(TRUE))); // FALSE -> TRUE checkHasNext(true, g.inject(1).not(is(FALSE))); - // ERROR -> ERROR -> FALSE - checkHasNext(false, g.inject(1).not(is(ERROR))); + // ERROR -> FALSE -> TRUE + checkHasNext(true, g.inject(1).not(is(ERROR))); // Binary reduction with NaN checkHasNext(false, g.inject(1).is(P.eq(Double.NaN))); checkHasNext(true, g.inject(1).is(P.neq(Double.NaN))); checkHasNext(true, g.inject(1).not(is(P.eq(Double.NaN)))); checkHasNext(false, g.inject(1).not(not(is(P.eq(Double.NaN))))); - checkHasNext(false, g.inject(1).where(__.inject(1).not(is(ERROR)))); + checkHasNext(true, g.inject(1).where(__.inject(1).not(is(ERROR)))); } /** @@ -302,39 +302,21 @@ public class TernaryBooleanLogicsTest extends AbstractGremlinProcessTest { checkHasNext(false, g.inject(1).filter(xor.apply(TRUE, TRUE))); // TRUE, FALSE -> TRUE checkHasNext(true, g.inject(1).filter(xor.apply(TRUE, FALSE))); - // TRUE, ERROR -> ERROR -> FALSE - checkHasNext(false, g.inject(1).filter(xor.apply(TRUE, ERROR))); + // TRUE, ERROR -> TRUE, FALSE -> TRUE + checkHasNext(true, g.inject(1).filter(xor.apply(TRUE, ERROR))); // FALSE, TRUE -> TRUE checkHasNext(true, g.inject(1).filter(xor.apply(FALSE, TRUE))); // FALSE, FALSE -> FALSE checkHasNext(false, g.inject(1).filter(xor.apply(FALSE, FALSE))); - // FALSE, ERROR -> ERROR -> FALSE + // FALSE, ERROR -> FALSE, FALSE -> FALSE checkHasNext(false, g.inject(1).filter(xor.apply(FALSE, ERROR))); - // ERROR, TRUE -> ERROR -> FALSE - checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, TRUE))); - // ERROR, FALSE -> ERROR -> FALSE + // ERROR, TRUE -> FALSE, TRUE -> TRUE + checkHasNext(true, g.inject(1).filter(xor.apply(ERROR, TRUE))); + // ERROR, FALSE -> FALSE, FALSE -> FALSE checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, FALSE))); - // ERROR, ERROR -> ERROR -> FALSE + // ERROR, ERROR -> FALSE, FALSE -> FALSE checkHasNext(false, g.inject(1).filter(xor.apply(ERROR, ERROR))); } - - /** - * Child traversals should propagate error states to their parent traversal if and only if the parent step is a - * filter step. - */ - @Test - @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS) - public void testErrorPropagation() { - final P ERROR = P.lt(Double.NaN); - - // Propagates Error to parent not() - checkHasNext(false, g.inject(1).not(not(is(ERROR)))); - checkHasNext(false, g.inject(1).not(is(ERROR))); - - // Does not propagate Error through non-filter parent - checkHasNext(true, g.inject(1).not(union((is(ERROR))))); - checkHasNext(false, g.inject(1).union((is(ERROR)))); - } } diff --git a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Comparability.feature b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Comparability.feature index 3059393412..8963199cad 100644 --- a/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Comparability.feature +++ b/gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/semantics/Comparability.feature @@ -834,14 +834,16 @@ Feature: Comparability | d[1].d | @GraphComputerVerificationInjectionNotSupported - Scenario: InjectX1dX_notXerrorX + Scenario: InjectX1dX_notXNaNX Given the empty graph And the traversal of """ g.inject(1d).not(is(P.gt(NaN))) """ When iterated to list - Then the result should be empty + Then the result should be unordered + | result | + | d[1].d | @GraphComputerVerificationInjectionNotSupported Scenario: InjectX1dX_notXisXeqXNaNXXX diff --git a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java index f56903d166..a9615f6c07 100644 --- a/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java +++ b/tinkergraph-gremlin/src/main/java/org/apache/tinkerpop/gremlin/tinkergraph/process/traversal/step/sideEffect/TinkerGraphStep.java @@ -19,10 +19,8 @@ package org.apache.tinkerpop.gremlin.tinkergraph.process.traversal.step.sideEffect; import org.apache.tinkerpop.gremlin.process.traversal.Compare; -import org.apache.tinkerpop.gremlin.process.traversal.GremlinTypeErrorException; import org.apache.tinkerpop.gremlin.process.traversal.P; import org.apache.tinkerpop.gremlin.process.traversal.step.HasContainerHolder; -import org.apache.tinkerpop.gremlin.process.traversal.step.filter.FilterStep; import org.apache.tinkerpop.gremlin.process.traversal.step.map.GraphStep; import org.apache.tinkerpop.gremlin.process.traversal.step.util.HasContainer; import org.apache.tinkerpop.gremlin.process.traversal.util.AndP; @@ -134,21 +132,8 @@ public final class TinkerGraphStep<S, E extends Element> extends GraphStep<S, E> try { while (iterator.hasNext()) { final E e = iterator.next(); - try { - if (HasContainer.testAll(e, this.hasContainers)) - list.add(e); - } catch (GremlinTypeErrorException ex) { - if (getTraversal().isRoot() || !(getTraversal().getParent() instanceof FilterStep)) { - /* - * Either we are at the top level of the query, or our parent query is not a FilterStep and thus - * cannot handle a GremlinTypeErrorException. In any of these cases we do a binary reduction - * from ERROR -> FALSE and filter the solution quietly. - */ - } else { - // not a ternary -> binary reducer, pass the ERROR on - throw ex; - } - } + if (HasContainer.testAll(e, this.hasContainers)) + list.add(e); } } finally { // close the old iterator to release resources since we are returning a new iterator (over list)
