From 6555f20b328ce286dfef92d57bcd82a9fcbda955 Mon Sep 17 00:00:00 2001 From: Henrik Nyman Date: Mon, 26 Mar 2018 02:59:15 +0200 Subject: [PATCH] Extend math helper test with Values --- .../internal/codegen/CompiledMathHelper.java | 87 ++-- .../codegen/CompiledMathHelperTest.scala | 424 +++++++++++++++++- 2 files changed, 469 insertions(+), 42 deletions(-) diff --git a/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/CompiledMathHelper.java b/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/CompiledMathHelper.java index 50510d6ee7dab..20103abf82805 100644 --- a/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/CompiledMathHelper.java +++ b/community/cypher/cypher/src/main/java/org/neo4j/cypher/internal/codegen/CompiledMathHelper.java @@ -31,6 +31,8 @@ import org.neo4j.values.storable.IntegralValue; import org.neo4j.values.storable.NumberValue; import org.neo4j.values.storable.TextValue; +import org.neo4j.values.storable.Value; +import org.neo4j.values.storable.Values; import org.neo4j.values.virtual.ListValue; import org.neo4j.values.virtual.VirtualValues; @@ -54,7 +56,7 @@ private CompiledMathHelper() */ public static Object add( Object lhs, Object rhs ) { - if ( lhs == null || rhs == null ) + if ( lhs == null || rhs == null || lhs == Values.NO_VALUE || rhs == Values.NO_VALUE ) { return null; } @@ -132,17 +134,24 @@ else if ( rhs instanceof List ) } if ( lhs instanceof String ) { - if ( rhs instanceof String ) + if ( rhs instanceof Value ) + { + return String.valueOf( lhs ) + ((Value) rhs).prettyPrint(); + } + else { return String.valueOf( lhs ) + String.valueOf( rhs ); } - else if ( rhs instanceof NumberValue ) + } + if ( rhs instanceof String ) + { + if ( lhs instanceof Value ) { - return String.valueOf( lhs ) + ((NumberValue) rhs).prettyPrint(); + return ((Value) lhs).prettyPrint() + String.valueOf( rhs ); } - else if ( rhs instanceof Number ) + else { - return String.valueOf( lhs ) + rhs.toString(); + return lhs.toString() + String.valueOf( rhs ); } } @@ -187,26 +196,29 @@ else if ( rhsClass.isArray() ) rhs = ((NumberValue) rhs).asObject(); } - if ( lhs instanceof Number && rhs instanceof Number ) + if ( lhs instanceof Number ) { - if ( lhs instanceof Double || rhs instanceof Double || - lhs instanceof Float || rhs instanceof Float ) + if ( rhs instanceof Number ) { - return ((Number) lhs).doubleValue() + ((Number) rhs).doubleValue(); - } - if ( lhs instanceof Long || rhs instanceof Long || - lhs instanceof Integer || rhs instanceof Integer || - lhs instanceof Short || rhs instanceof Short || - lhs instanceof Byte || rhs instanceof Byte ) - { - return Math.addExact( ((Number) lhs).longValue(), ((Number) rhs).longValue() ); - // Remap java.lang.ArithmeticException later instead of: - //catch ( java.lang.ArithmeticException e ) - //{ - // throw new ArithmeticException( - // String.format( "result of %d + %d cannot be represented as an integer", - // ((Number) lhs).longValue(), ((Number) rhs).longValue() ), e ); - //} + if ( lhs instanceof Double || rhs instanceof Double || + lhs instanceof Float || rhs instanceof Float ) + { + return ((Number) lhs).doubleValue() + ((Number) rhs).doubleValue(); + } + if ( lhs instanceof Long || rhs instanceof Long || + lhs instanceof Integer || rhs instanceof Integer || + lhs instanceof Short || rhs instanceof Short || + lhs instanceof Byte || rhs instanceof Byte ) + { + return Math.addExact( ((Number) lhs).longValue(), ((Number) rhs).longValue() ); + // Remap java.lang.ArithmeticException later instead of: + //catch ( java.lang.ArithmeticException e ) + //{ + // throw new ArithmeticException( + // String.format( "result of %d + %d cannot be represented as an integer", + // ((Number) lhs).longValue(), ((Number) rhs).longValue() ), e ); + //} + } } // other numbers we cannot add } @@ -217,7 +229,7 @@ else if ( rhsClass.isArray() ) public static Object subtract( Object lhs, Object rhs ) { - if ( lhs == null || rhs == null ) + if ( lhs == null || rhs == null || lhs == Values.NO_VALUE || rhs == Values.NO_VALUE ) { return null; } @@ -260,13 +272,13 @@ public static Object subtract( Object lhs, Object rhs ) // other numbers we cannot subtract } - throw new CypherTypeException( "Cannot add " + lhs.getClass().getSimpleName() + + throw new CypherTypeException( "Cannot subtract " + lhs.getClass().getSimpleName() + " and " + rhs.getClass().getSimpleName(), null ); } public static Object multiply( Object lhs, Object rhs ) { - if ( lhs == null || rhs == null ) + if ( lhs == null || rhs == null || lhs == Values.NO_VALUE || rhs == Values.NO_VALUE ) { return null; } @@ -315,7 +327,7 @@ public static Object multiply( Object lhs, Object rhs ) public static Object divide( Object lhs, Object rhs ) { - if ( lhs == null || rhs == null ) + if ( lhs == null || rhs == null || lhs == Values.NO_VALUE || rhs == Values.NO_VALUE ) { return null; } @@ -373,7 +385,7 @@ public static Object divide( Object lhs, Object rhs ) public static Object modulo( Object lhs, Object rhs ) { - if ( lhs == null || rhs == null ) + if ( lhs == null || rhs == null || lhs == Values.NO_VALUE || rhs == Values.NO_VALUE ) { return null; } @@ -390,15 +402,17 @@ public static Object modulo( Object lhs, Object rhs ) if ( lhs instanceof Number && rhs instanceof Number ) { - if ( lhs instanceof Double || rhs instanceof Double ) - { - return ((Number) lhs).doubleValue() % ((Number) rhs).doubleValue(); - } - else if ( lhs instanceof Float || rhs instanceof Float ) + if ( lhs instanceof Double || rhs instanceof Double || + lhs instanceof Float || rhs instanceof Float ) { - return ((Number) lhs).floatValue() % ((Number) rhs).floatValue(); + double left = ((Number) lhs).doubleValue(); + double right = ((Number) rhs).doubleValue(); + return left % right; } - else + if ( lhs instanceof Long || rhs instanceof Long || + lhs instanceof Integer || rhs instanceof Integer || + lhs instanceof Short || rhs instanceof Short || + lhs instanceof Byte || rhs instanceof Byte ) { long left = ((Number) lhs).longValue(); long right = ((Number) rhs).longValue(); @@ -408,6 +422,7 @@ else if ( lhs instanceof Float || rhs instanceof Float ) } return left % right; } + // other numbers we cannot divide } throw new CypherTypeException( "Cannot modulo " + lhs.getClass().getSimpleName() + diff --git a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/internal/codegen/CompiledMathHelperTest.scala b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/internal/codegen/CompiledMathHelperTest.scala index f7ee568937419..25ec39528d311 100644 --- a/community/cypher/cypher/src/test/scala/org/neo4j/cypher/internal/codegen/CompiledMathHelperTest.scala +++ b/community/cypher/cypher/src/test/scala/org/neo4j/cypher/internal/codegen/CompiledMathHelperTest.scala @@ -22,6 +22,10 @@ package org.neo4j.cypher.internal.codegen import java.util import org.neo4j.cypher.internal.util.v3_4.CypherTypeException +import org.neo4j.kernel.impl.util.ValueUtils +import org.neo4j.values.AnyValue +import org.neo4j.values.storable._ +import org.neo4j.values.virtual.{ListValue, VirtualValues} import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.{Assertions, Matchers, PropSpec} @@ -29,7 +33,7 @@ import scala.collection.JavaConverters._ class CompiledMathHelperTest extends PropSpec with TableDrivenPropertyChecks with Matchers with Assertions { - val values: Seq[AnyRef] = + val javaValues: Seq[AnyRef] = // The exclamation mark casts the expression to a AnyRef instead of a primitive // To be as exhaustive as possible, the strategy is to do a cartesian product of all test values, // and ensure that either we have defined behaviour, or that a runtime type exception is thrown @@ -44,11 +48,19 @@ class CompiledMathHelperTest extends PropSpec with TableDrivenPropertyChecks wit false ! ) + val neoValues: Seq[AnyRef] = javaValues.map(ValueUtils.of) + + val values: Seq[AnyRef] = javaValues ++ neoValues + property("+") { forAll(getTable(CompiledMathHelper.add)) { + // Nulls case (null, _, Right(result)) => result should equal(null) case (_, null, Right(result)) => result should equal(null) + case (Values.NO_VALUE, _, Right(result)) => result should equal(null) + case (_, Values.NO_VALUE, Right(result)) => result should equal(null) + // Java values case (l: java.lang.Double, r: Number, Right(result)) => result should equal(l + r.doubleValue()) case (l: Number, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() + r) case (l: java.lang.Float, r: Number, Right(result)) => result should equal(l + r.doubleValue()) @@ -56,7 +68,7 @@ class CompiledMathHelperTest extends PropSpec with TableDrivenPropertyChecks wit case (l: Number, r: Number, Right(result)) => result should equal(l.longValue() + r.longValue()) case (l: Number, r: String, Right(result)) => result should equal(l.toString + r) - case (l: String, r: Number, Right(result)) => result should equal(l.toString + r) + case (l: String, r: Number, Right(result)) => result should equal(l + r.toString) case (l: String, r: String, Right(result)) => result should equal(l + r) case (l: String, r: java.lang.Boolean, Right(result)) => result should equal(l + r) case (l: java.lang.Boolean, r: String, Right(result)) => result should equal(l + r) @@ -65,19 +77,94 @@ class CompiledMathHelperTest extends PropSpec with TableDrivenPropertyChecks wit case (_: java.lang.Boolean, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] case (_: java.lang.Boolean, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + // Neo values + case (l: DoubleValue, r: NumberValue, Right(result)) => + java.lang.Double.compare(result.asInstanceOf[NumberValue].doubleValue(), l.doubleValue() + r.doubleValue()) should equal(0) + case (l: NumberValue, r: DoubleValue, Right(result)) => + java.lang.Double.doubleToLongBits(result.asInstanceOf[NumberValue].doubleValue()) should equal( + java.lang.Double.doubleToLongBits(l.doubleValue() + r.doubleValue())) + case (l: FloatValue, r: NumberValue, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: NumberValue, r: FloatValue, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: NumberValue, r: NumberValue, Right(result)) => result should equal(Values.longValue(l.longValue() + r.longValue())) + + case (l: NumberValue, r: TextValue, Right(result)) => result should equal(l.prettyPrint() + r.stringValue()) + case (l: TextValue, r: NumberValue, Right(result)) => result should equal(l.stringValue() + r.prettyPrint()) + case (l: TextValue, r: TextValue, Right(result)) => result should equal(l.stringValue() + r.stringValue()) + case (l: TextValue, r: BooleanValue, Right(result)) => result should equal(l.stringValue() + r.booleanValue()) + case (l: BooleanValue, r: TextValue, Right(result)) => result should equal(l.booleanValue() + r.stringValue()) + + case (_: NumberValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Lists case (l1: util.List[_], l2: util.List[_], Right(result)) => result should equal(concat(l1, l2)) + case (l1: ListValue, l2: ListValue, Right(result)) => result should equal( + VirtualValues.fromList(concat(l1.asArray().toList.asJava, l1.asArray().toList.asJava).asInstanceOf[util.List[AnyValue]]) + ) + case (l1: ListValue, l2: util.List[_], Right(result)) => result should equal( + VirtualValues.fromList(concat(l1.asArray().toList.asJava, + ValueUtils.of(l2).asInstanceOf[ListValue].asArray.toList.asJava).asInstanceOf[util.List[AnyValue]]) + ) + case (l1: util.List[_], l2: ListValue, Right(result)) => result should equal( + VirtualValues.fromList(concat(ValueUtils.of(l1).asInstanceOf[ListValue].asArray.toList.asJava, + l2.asArray().toList.asJava).asInstanceOf[util.List[AnyValue]]) + ) + case (x, l: ListValue, Right(result)) => result should equal( + VirtualValues.fromList(prepend(ValueUtils.asAnyValue(x), l.asArray().toList.asJava).asInstanceOf[util.List[AnyValue]]) + ) + case (l: ListValue, x, Right(result)) => result should equal( + VirtualValues.fromList(append(ValueUtils.asAnyValue(x), l.asArray().toList.asJava).asInstanceOf[util.List[AnyValue]]) + ) case (x, l: util.List[_], Right(result)) => result should equal(prepend(x, l)) case (l: util.List[_], x, Right(result)) => result should equal(append(x, l)) - case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 + $v2 => $v3") + // Mix of Neo values and Java values + case (l: DoubleValue, r: Number, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: Number, r: DoubleValue, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: NumberValue, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: java.lang.Double, r: NumberValue, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: FloatValue, r: Number, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: Number, r: FloatValue, Right(result)) => result should equal(l.doubleValue() + r.doubleValue()) + case (l: java.lang.Float, r: NumberValue, Right(result)) => result should equal(l + r.doubleValue()) + case (l: NumberValue, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() + r) + case (l: NumberValue, r: Number, Right(result)) => result should equal(l.longValue() + r.longValue()) + case (l: Number, r: NumberValue, Right(result)) => result should equal(l.longValue() + r.longValue()) + + case (l: NumberValue, r: String, Right(result)) => result should equal(l.prettyPrint() + r) + case (l: String, r: NumberValue, Right(result)) => result should equal(l + r.prettyPrint()) + case (l: TextValue, r: Number, Right(result)) => result should equal(l.stringValue() + r.toString) + case (l: Number, r: TextValue, Right(result)) => result should equal(l.toString() + r.stringValue()) + case (l: TextValue, r: String, Right(result)) => result should equal(l.stringValue() + r) + case (l: String, r: TextValue, Right(result)) => result should equal(l + r.stringValue()) + case (l: TextValue, r: java.lang.Boolean, Right(result)) => result should equal(l.stringValue() + r) + case (l: java.lang.Boolean, r: TextValue, Right(result)) => result should equal(l + r.stringValue()) + case (l: BooleanValue, r: String, Right(result)) => + result should equal(l.booleanValue() + r) + case (l: String, r: BooleanValue, Right(result)) => + result should equal(l + r.booleanValue()) + + case (_: NumberValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: Number, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (v1, v2, v3) => + fail(s"Unspecified behaviour: $v1 + $v2 => $v3") } } property("-") { forAll(getTable(CompiledMathHelper.subtract)) { - case (null, _, Right( result )) => result should equal( null ) - case (_, null, Right( result )) => result should equal( null ) + // Nulls + case (null, _, Right(result)) => result should equal(null) + case (_, null, Right(result)) => result should equal(null) + case (Values.NO_VALUE, _, Right(result)) => result should equal(null) + case (_, Values.NO_VALUE, Right(result)) => result should equal(null) + // Java values case (l: java.lang.Double, r: Number, Right(result)) => result should equal(l - r.doubleValue()) case (l: Number, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() - r) case (l: java.lang.Float, r: Number, Right(result)) => result should equal(l - r.doubleValue()) @@ -94,11 +181,336 @@ class CompiledMathHelperTest extends PropSpec with TableDrivenPropertyChecks wit case (_: java.lang.Boolean, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] case (_: java.lang.Boolean, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + // Neo values + case (l: DoubleValue, r: NumberValue, Right(result)) => + java.lang.Double.compare(result.asInstanceOf[NumberValue].doubleValue(), l.doubleValue() - r.doubleValue()) should equal(0) + case (l: NumberValue, r: DoubleValue, Right(result)) => + java.lang.Double.doubleToLongBits(result.asInstanceOf[NumberValue].doubleValue()) should equal( + java.lang.Double.doubleToLongBits(l.doubleValue() - r.doubleValue())) + case (l: FloatValue, r: NumberValue, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: NumberValue, r: FloatValue, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: NumberValue, r: NumberValue, Right(result)) => result should equal(Values.longValue(l.longValue() - r.longValue())) + + case (l: NumberValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Lists case (l1: util.List[_], l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: util.List[_], l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: ListValue, x, Left(exception)) => exception shouldBe a [CypherTypeException] case (x, l: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] case (l: util.List[_], x, Left(exception)) => exception shouldBe a [CypherTypeException] - case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 + $v2 => $v3") + // Mix of Neo values and Java values + case (l: DoubleValue, r: Number, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: Number, r: DoubleValue, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: NumberValue, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: java.lang.Double, r: NumberValue, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: FloatValue, r: Number, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: Number, r: FloatValue, Right(result)) => result should equal(l.doubleValue() - r.doubleValue()) + case (l: java.lang.Float, r: NumberValue, Right(result)) => result should equal(l - r.doubleValue()) + case (l: NumberValue, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() - r) + case (l: NumberValue, r: Number, Right(result)) => result should equal(l.longValue() - r.longValue()) + case (l: Number, r: NumberValue, Right(result)) => result should equal(l.longValue() - r.longValue()) + + case (l: NumberValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: Number, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: Number, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 - $v2 => $v3") + } + } + + property("*") { + forAll(getTable(CompiledMathHelper.multiply)) { + // Nulls + case (null, _, Right(result)) => result should equal(null) + case (_, null, Right(result)) => result should equal(null) + case (Values.NO_VALUE, _, Right(result)) => result should equal(null) + case (_, Values.NO_VALUE, Right(result)) => result should equal(null) + + // Java values + case (l: java.lang.Double, r: Number, Right(result)) => result should equal(l * r.doubleValue()) + case (l: Number, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() * r) + case (l: java.lang.Float, r: Number, Right(result)) => result should equal(l * r.doubleValue()) + case (l: Number, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() * r) + case (l: Number, r: Number, Right(result)) => result should equal(l.longValue() * r.longValue()) + + case (l: Number, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: Number, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Neo values + case (l: DoubleValue, r: NumberValue, Right(result)) => + java.lang.Double.compare(result.asInstanceOf[NumberValue].doubleValue(), l.doubleValue() * r.doubleValue()) should equal(0) + case (l: NumberValue, r: DoubleValue, Right(result)) => + java.lang.Double.doubleToLongBits(result.asInstanceOf[NumberValue].doubleValue()) should equal( + java.lang.Double.doubleToLongBits(l.doubleValue() * r.doubleValue())) + case (l: FloatValue, r: NumberValue, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: NumberValue, r: FloatValue, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: NumberValue, r: NumberValue, Right(result)) => result should equal(Values.longValue(l.longValue() * r.longValue())) + + case (l: NumberValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Lists + case (l1: util.List[_], l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: util.List[_], l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: ListValue, x, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: util.List[_], x, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Mix of Neo values and Java values + case (l: DoubleValue, r: Number, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: Number, r: DoubleValue, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: NumberValue, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: java.lang.Double, r: NumberValue, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: FloatValue, r: Number, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: Number, r: FloatValue, Right(result)) => result should equal(l.doubleValue() * r.doubleValue()) + case (l: java.lang.Float, r: NumberValue, Right(result)) => result should equal(l * r.doubleValue()) + case (l: NumberValue, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() * r) + case (l: NumberValue, r: Number, Right(result)) => result should equal(l.longValue() * r.longValue()) + case (l: Number, r: NumberValue, Right(result)) => result should equal(l.longValue() * r.longValue()) + + case (l: NumberValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: Number, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: Number, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 * $v2 => $v3") + } + } + + property("/") { + forAll(getTable(CompiledMathHelper.divide)) { + // Nulls + case (null, _, Right(result)) => result should equal(null) + case (_, null, Right(result)) => result should equal(null) + case (Values.NO_VALUE, _, Right(result)) => result should equal(null) + case (_, Values.NO_VALUE, Right(result)) => result should equal(null) + + // Java values + case (l: java.lang.Double, r: Number, Right(result)) => result should equal(l / r.doubleValue()) + case (l: Number, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() / r) + case (l: java.lang.Float, r: Number, Right(result)) => result should equal(l / r.doubleValue()) + case (l: Number, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() / r) + case (l: Number, r: Number, Right(result)) => result should equal(l.longValue() / r.longValue()) + + case (l: Number, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: Number, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Neo values + case (l: DoubleValue, r: NumberValue, Right(result)) => + java.lang.Double.compare(result.asInstanceOf[NumberValue].doubleValue(), l.doubleValue() / r.doubleValue()) should equal(0) + case (l: NumberValue, r: DoubleValue, Right(result)) => + java.lang.Double.doubleToLongBits(result.asInstanceOf[NumberValue].doubleValue()) should equal( + java.lang.Double.doubleToLongBits(l.doubleValue() / r.doubleValue())) + case (l: FloatValue, r: NumberValue, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: NumberValue, r: FloatValue, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: NumberValue, r: NumberValue, Right(result)) => result should equal(Values.longValue(l.longValue() / r.longValue())) + + case (l: NumberValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Lists + case (l1: util.List[_], l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: util.List[_], l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: ListValue, x, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: util.List[_], x, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Mix of Neo values and Java values + case (l: DoubleValue, r: Number, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: Number, r: DoubleValue, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: NumberValue, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: java.lang.Double, r: NumberValue, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: FloatValue, r: Number, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: Number, r: FloatValue, Right(result)) => result should equal(l.doubleValue() / r.doubleValue()) + case (l: java.lang.Float, r: NumberValue, Right(result)) => result should equal(l / r.doubleValue()) + case (l: NumberValue, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() / r) + case (l: NumberValue, r: Number, Right(result)) => result should equal(l.longValue() / r.longValue()) + case (l: Number, r: NumberValue, Right(result)) => result should equal(l.longValue() / r.longValue()) + + case (l: NumberValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: Number, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: Number, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 / $v2 => $v3") + } + } + + property("%") { + forAll(getTable(CompiledMathHelper.modulo)) { + // Nulls + case (null, _, Right(result)) => result should equal(null) + case (_, null, Right(result)) => result should equal(null) + case (Values.NO_VALUE, _, Right(result)) => result should equal(null) + case (_, Values.NO_VALUE, Right(result)) => result should equal(null) + + // Java values + case (l: java.lang.Double, r: Number, Right(result)) => result should equal(l % r.doubleValue()) + case (l: Number, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() % r) + case (l: java.lang.Float, r: Number, Right(result)) => result should equal(l % r.doubleValue()) + case (l: Number, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() % r) + case (l: Number, r: Number, Right(result)) => result should equal(l.longValue() % r.longValue()) + + case (l: Number, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: Number, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Neo values + // NOTE: We do not produce any Values here as with the other numerical operators + case (l: DoubleValue, r: NumberValue, Right(result)) => + java.lang.Double.compare(result.asInstanceOf[Double], l.doubleValue() % r.doubleValue()) should equal(0) + case (l: NumberValue, r: DoubleValue, Right(result)) => + java.lang.Double.doubleToLongBits(result.asInstanceOf[Number].doubleValue()) should equal( + java.lang.Double.doubleToLongBits(l.doubleValue() % r.doubleValue())) + case (l: FloatValue, r: NumberValue, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: NumberValue, r: FloatValue, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: NumberValue, r: NumberValue, Right(result)) => + result should equal(l.longValue() % r.longValue()) + + case (l: NumberValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Lists + case (l1: util.List[_], l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: ListValue, l2: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l1: util.List[_], l2: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: ListValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: ListValue, x, Left(exception)) => exception shouldBe a [CypherTypeException] + case (x, l: util.List[_], Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: util.List[_], x, Left(exception)) => exception shouldBe a [CypherTypeException] + + // Mix of Neo values and Java values + case (l: DoubleValue, r: Number, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: Number, r: DoubleValue, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: NumberValue, r: java.lang.Double, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: java.lang.Double, r: NumberValue, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: FloatValue, r: Number, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: Number, r: FloatValue, Right(result)) => result should equal(l.doubleValue() % r.doubleValue()) + case (l: java.lang.Float, r: NumberValue, Right(result)) => result should equal(l % r.doubleValue()) + case (l: NumberValue, r: java.lang.Float, Right(result)) => result should equal(l.doubleValue() % r) + case (l: NumberValue, r: Number, Right(result)) => result should equal(l.longValue() % r.longValue()) + case (l: Number, r: NumberValue, Right(result)) => result should equal(l.longValue() % r.longValue()) + + case (l: NumberValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: Number, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: TextValue, r: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: java.lang.Boolean, r: TextValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: BooleanValue, r: String, Left(exception)) => exception shouldBe a [CypherTypeException] + case (l: String, r: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (_: NumberValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: NumberValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: java.lang.Boolean, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: java.lang.Boolean, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: BooleanValue, _: Number, Left(exception)) => exception shouldBe a [CypherTypeException] + case (_: Number, _: BooleanValue, Left(exception)) => exception shouldBe a [CypherTypeException] + + case (v1, v2, v3) => fail(s"Unspecified behaviour: $v1 / $v2 => $v3") } }