You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by GitBox <gi...@apache.org> on 2022/01/24 03:06:27 UTC

[GitHub] [tinkerpop] mschmidt00 commented on a change in pull request #1553: Gremlin Value Expressions 2.0 / Ternary Boolean Logics

mschmidt00 commented on a change in pull request #1553:
URL: https://github.com/apache/tinkerpop/pull/1553#discussion_r790386184



##########
File path: gremlin-test/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/TernaryBooleanLogicsTest.java
##########
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal.step;
+
+import org.apache.tinkerpop.gremlin.FeatureRequirement;
+import org.apache.tinkerpop.gremlin.process.AbstractGremlinProcessTest;
+import org.apache.tinkerpop.gremlin.process.GremlinProcessRunner;
+import org.apache.tinkerpop.gremlin.process.traversal.P;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import org.hamcrest.core.Is;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.BiFunction;
+
+import static org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__.*;
+import static org.apache.tinkerpop.gremlin.structure.Graph.Features.GraphFeatures;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(GremlinProcessRunner.class)
+public class TernaryBooleanLogicsTest extends AbstractGremlinProcessTest {
+
+    /**
+     * NaN comparisons always produce UNDEF comparison, reducing to FALSE in ternary->binary reduction.
+     */
+    @Test
+    @FeatureRequirement(featureClass = GraphFeatures.class, feature = GraphFeatures.FEATURE_ORDERABILITY_SEMANTICS)
+    public void testCompareNaN() {
+        // NaN vs. NaN
+        // P.eq
+        checkHasNext(false, g.inject(Double.NaN).is(P.eq(Double.NaN)));
+        // P.neq
+        checkHasNext(true, g.inject(Double.NaN).is(P.neq(Double.NaN)));
+        // P.lt
+        checkHasNext(false, g.inject(Double.NaN).is(P.lt(Double.NaN)));
+        // P.lte
+        checkHasNext(false, g.inject(Double.NaN).is(P.lte(Double.NaN)));
+        // P.gt
+        checkHasNext(false, g.inject(Double.NaN).is(P.gt(Double.NaN)));
+        // P.gte
+        checkHasNext(false, g.inject(Double.NaN).is(P.gte(Double.NaN)));

Review comment:
       Does it make sense to also add some tests for nested predicates? Something like "1<2 == 2<3", with more interesting values such as NULL's an NaN's?

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/filter/FilterStep.java
##########
@@ -34,9 +36,22 @@ public FilterStep(final Traversal.Admin traversal) {
     @Override
     protected Traverser.Admin<S> processNextStart() {
         while (true) {
-            final Traverser.Admin<S> traverser = this.starts.next();
-            if (this.filter(traverser))
-                return traverser;
+            try {
+                final Traverser.Admin<S> traverser = this.starts.next();
+                if (this.filter(traverser))
+                    return traverser;
+            } catch (GremlinTypeErrorException ex) {
+                if (this instanceof BinaryReductionStep || getTraversal().getParent() == EmptyStep.instance()) {

Review comment:
       Yep, agreed this looks critical. I am wondering though why we couldn't *always* forward the exception here and catch it in the target steps where we want to transform the exception into FALSE. Might be a little more code on the one hand, but a little more explicit and safe (in the sense that it is "guaranteed top-level") on the other hand. Give it a thought, but certainly trusting in @spmallette and you in case you both agree this is failsafe.

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java
##########
@@ -103,11 +101,20 @@
     private static final Comparator<Map.Entry> entryComparator =
             Comparator.<Map.Entry,Object>comparing(Map.Entry::getKey, INSTANCE).thenComparing(Map.Entry::getValue, INSTANCE);
 
+
+
+    private static boolean bothAreComparableAndOfSameType(Object first, Object second) {

Review comment:
       Name is a bit misleading, plus I do not fully understand how this works. Is the idea here that numbers would be considered the same type because one class is instance of another one? In any case, some documentation and examples would be helpful.

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java
##########
@@ -52,9 +52,19 @@
     within {
         @Override
         public boolean test(final Object first, final Collection second) {
-            return first instanceof Number
-                    ? IteratorUtils.anyMatch(second.iterator(), o -> Compare.eq.test(first, o))
-                    : 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

Review comment:
       +1, makes sense

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/PTest.java
##########
@@ -158,6 +163,20 @@
                     {TextP.containing("o").and(P.gte("j")).and(TextP.endingWith("ko")), "josh", false},
                     {TextP.containing("o").and(P.gte("j").and(TextP.endingWith("ko"))), "marko", true},
                     {TextP.containing("o").and(P.gte("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(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.containing("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.containing(null), null, GremlinTypeErrorException.class},
+                    {TextP.startingWith(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.startingWith("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.startingWith(null), null, GremlinTypeErrorException.class},
+                    {TextP.endingWith(null), "abc", GremlinTypeErrorException.class},
+                    {TextP.endingWith("abc"), null, GremlinTypeErrorException.class},
+                    {TextP.endingWith(null), null, GremlinTypeErrorException.class},

Review comment:
       nit: recommend a few tests with other (i.e., composite) types

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
##########
@@ -95,7 +87,96 @@
                 {Compare.gte, new B(), new C(), true},
                 {Compare.lte, new B(), new D(), true},
                 {Compare.lte, new C(), new C(), true},
-                {Compare.lte, new D(), new D(), true}
+                {Compare.lte, new D(), new D(), true},
+
+                // type promotion
+                {Compare.eq,  1, 1.0d, true},
+                {Compare.neq, 1, 1.0d, false},
+                {Compare.lt,  1, 1.0d, false},
+                {Compare.lte, 1, 1.0d, true},
+                {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},
+
+                /*
+                 * 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
+                 */
+                {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.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.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},
+
+                /*
+                 * We consider null to be in its own type space, and thus not comparable (lt/lte/gt/gte) with
+                 * anything other than null:
+                 *
+                 * 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
+                 */
+                {Compare.eq,  null, null, true},
+                {Compare.neq, null, null, false},
+                {Compare.gt,  null, null, false},
+                {Compare.gte, null, null, true},
+                {Compare.lt,  null, null, false},
+                {Compare.lte, null, null, true},
+
+                {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.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},
+

Review comment:
       Should we also add some tests for composite types? Apart from this being a gap, we should assert that lists etc. "inherit" the comparison semantics from the atomic cases (where needed, recursively). Writing test coverage is certainly an open-ended effort, but having a handful of tests for lists, maps, etc. would be important. 

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Compare.java
##########
@@ -78,25 +77,19 @@ public Compare negate() {
     },
 
     /**
-     * Evaluates if the first object is greater than the second. If both are of type {@link Number}, {@link NumberHelper}
-     * will be used for the comparison, thus enabling the comparison of only values, ignoring the number types.
+     * Evaluates if the first object is greater than the second per Gremlin Comparison semantics.
      *
      * @since 3.0.0-incubating
      */
     gt {
         @Override
         public boolean test(final Object first, final Object second) {
-            if (null != first && null != second) {
-                if (bothAreNumber(first, second)) {
-                    return NumberHelper.compare((Number) first, (Number) second) > 0;
-                }
-                if (bothAreComparableAndOfSameType(first, second)) {
-                    return ((Comparable) first).compareTo(second) > 0;
-                }
-                throwException(first, second);
-            }
-
-            return false;
+            // NaN takes precedence
+            if (eitherAreNaN(first, second))
+                throwTypeError();

Review comment:
       Haven't gotten to the implementation yet, but I assume this throws an exception internally. Personally, I'm not a big fan of using exceptions in (somewhat) regular code paths, but this is admittedly somewhere in-between (in the sense that it would only happen in edge cases). Something to think about, but if you feel that's the best way to implement it I'm fine going forward with it. 

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/util/OrderabilityComparator.java
##########
@@ -167,6 +174,20 @@ private static Object transform(Object o) {
         return o;
     }
 
+    /**
+     * Return true if the two objects are of the same comparison type, even if they are not the same Class.
+     */
+    public static boolean comparable(final Object f, final Object s) {
+        if (f == null || s == null)
+            return f == s; // both in the null space
+
+        final Type ft = Type.type(f);
+        final Type st = Type.type(s);
+
+        // if they're both in the unknown type space then only return true if they are naturally Comparable
+        return ft == Type.Unknown && st == Type.Unknown ? bothAreComparableAndOfSameType(f, s) : ft == st;

Review comment:
       Don't understand this, could you explain? What is the "unknown type space", i.e. why would we have unknown types at runtime?

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
##########
@@ -95,7 +87,96 @@
                 {Compare.gte, new B(), new C(), true},
                 {Compare.lte, new B(), new D(), true},
                 {Compare.lte, new C(), new C(), true},
-                {Compare.lte, new D(), new D(), true}
+                {Compare.lte, new D(), new D(), true},
+
+                // type promotion
+                {Compare.eq,  1, 1.0d, true},
+                {Compare.neq, 1, 1.0d, false},
+                {Compare.lt,  1, 1.0d, false},
+                {Compare.lte, 1, 1.0d, true},
+                {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},
+
+                /*
+                 * 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
+                 */
+                {Compare.eq,  NaN, NaN, false},

Review comment:
       Those are still TBD I assume (did not review the NaN tests for now).

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
##########
@@ -117,6 +121,8 @@ public Text negate() {
     containing {
         @Override
         public boolean test(final String value, final String search) {
+            if (value == null || search == null)

Review comment:
       nit: seeing this if (...) => throw for the third time, consider factoring it into a throwTypeErrorIfNull() method?

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
##########
@@ -95,7 +87,96 @@
                 {Compare.gte, new B(), new C(), true},
                 {Compare.lte, new B(), new D(), true},
                 {Compare.lte, new C(), new C(), true},
-                {Compare.lte, new D(), new D(), true}
+                {Compare.lte, new D(), new D(), true},
+
+                // type promotion
+                {Compare.eq,  1, 1.0d, true},
+                {Compare.neq, 1, 1.0d, false},
+                {Compare.lt,  1, 1.0d, false},
+                {Compare.lte, 1, 1.0d, true},
+                {Compare.gt,  1, 1.0d, false},
+                {Compare.gte, 1, 1.0d, true},

Review comment:
       nit: recommend adding some tests for +/- INF values as well

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ContainsTest.java
##########
@@ -47,7 +52,12 @@
                 {Contains.within, 100, Arrays.asList(1, 2, 3, 4, 10), false},
                 {Contains.without, 10L, Arrays.asList(1, 2, 3, 4, 10), false},
                 {Contains.within, "test", Arrays.asList(1, 2, 3, "test", 10), true},
-                {Contains.without, "testing", Arrays.asList(1, 2, 3, "test", 10), true}
+                {Contains.without, "testing", Arrays.asList(1, 2, 3, "test", 10), true},
+
+                {Contains.within, Double.NaN, Arrays.asList(Double.NaN), false},
+                {Contains.within, Double.NaN, Arrays.asList(0, Double.NaN), false},
+                {Contains.without, Double.NaN, Arrays.asList(Double.NaN), true},
+                {Contains.without, Double.NaN, Arrays.asList(0, Double.NaN), true},

Review comment:
       Could add some exception tests here as well? 

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Contains.java
##########
@@ -52,9 +52,19 @@
     within {
         @Override
         public boolean test(final Object first, final Collection second) {
-            return first instanceof Number
-                    ? IteratorUtils.anyMatch(second.iterator(), o -> Compare.eq.test(first, o))
-                    : second.contains(first);
+            GremlinTypeErrorException typeError = null;

Review comment:
       Bit surprised to see you made changes to within but not to without -- wouldn't those also be required (or is the semantics for without that you would *always* forward an error -- the alternative would be to never forward it, I assume)? 

##########
File path: gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/Text.java
##########
@@ -37,6 +37,8 @@
     startingWith {
         @Override
         public boolean test(final String value, final String prefix) {

Review comment:
       I assume the cases where value or prefix are NOT a valid string would not even end up here but trigger an exception before already? Just in case this is not covered, I'd recommend some test cases for both value and prefix not being a string. Same for the other string functions below.

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/ConnectiveTest.java
##########
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.tinkerpop.gremlin.process.traversal;
+
+import org.apache.tinkerpop.gremlin.process.traversal.util.AndP;
+import org.apache.tinkerpop.gremlin.process.traversal.util.OrP;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author Mike Personick (http://github.com/mikepersonick)
+ */
+@RunWith(Enclosed.class)
+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);

Review comment:
       nice test!

##########
File path: gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/CompareTest.java
##########
@@ -95,7 +87,96 @@
                 {Compare.gte, new B(), new C(), true},
                 {Compare.lte, new B(), new D(), true},
                 {Compare.lte, new C(), new C(), true},
-                {Compare.lte, new D(), new D(), true}
+                {Compare.lte, new D(), new D(), true},
+
+                // type promotion
+                {Compare.eq,  1, 1.0d, true},
+                {Compare.neq, 1, 1.0d, false},
+                {Compare.lt,  1, 1.0d, false},
+                {Compare.lte, 1, 1.0d, true},
+                {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},
+
+                /*
+                 * 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
+                 */
+                {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.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.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},
+
+                /*
+                 * We consider null to be in its own type space, and thus not comparable (lt/lte/gt/gte) with
+                 * anything other than null:
+                 *
+                 * 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
+                 */
+                {Compare.eq,  null, null, true},
+                {Compare.neq, null, null, false},
+                {Compare.gt,  null, null, false},
+                {Compare.gte, null, null, true},
+                {Compare.lt,  null, null, false},
+                {Compare.lte, null, null, true},
+
+                {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.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.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},
         }));

Review comment:
       Another thing that I am missing is tests (as well as changes) for negation.  I was a bit surprised to realize there is no P.not predicate (wondering if it should be added), but only a not() step. This sort of implies that there's no nesting of negation at predicate level, and I assume that's the reason why we don't need any changes?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscribe@tinkerpop.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org