You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/02/07 11:26:02 UTC
cayenne git commit: CAY-2221 Comparisons with NULL values (include
CAY-1926)
Repository: cayenne
Updated Branches:
refs/heads/master c2d815dd0 -> 8e43461e4
CAY-2221 Comparisons with NULL values (include CAY-1926)
Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/8e43461e
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/8e43461e
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/8e43461e
Branch: refs/heads/master
Commit: 8e43461e4e473ae3137e5b263d50df3e2448206c
Parents: c2d815d
Author: Nikita Timofeev <st...@gmail.com>
Authored: Tue Feb 7 14:25:50 2017 +0300
Committer: Nikita Timofeev <st...@gmail.com>
Committed: Tue Feb 7 14:25:50 2017 +0300
----------------------------------------------------------------------
.../java/org/apache/cayenne/exp/Expression.java | 2 +-
.../org/apache/cayenne/exp/parser/ASTAnd.java | 13 ++-
.../apache/cayenne/exp/parser/ASTBetween.java | 2 +-
.../org/apache/cayenne/exp/parser/ASTEqual.java | 4 +-
.../org/apache/cayenne/exp/parser/ASTFalse.java | 4 +-
.../apache/cayenne/exp/parser/ASTGreater.java | 7 +-
.../cayenne/exp/parser/ASTGreaterOrEqual.java | 7 +-
.../org/apache/cayenne/exp/parser/ASTIn.java | 13 ++-
.../org/apache/cayenne/exp/parser/ASTLess.java | 7 +-
.../cayenne/exp/parser/ASTLessOrEqual.java | 7 +-
.../org/apache/cayenne/exp/parser/ASTLike.java | 2 +-
.../cayenne/exp/parser/ASTLikeIgnoreCase.java | 2 +-
.../org/apache/cayenne/exp/parser/ASTNot.java | 7 +-
.../cayenne/exp/parser/ASTNotBetween.java | 2 +-
.../apache/cayenne/exp/parser/ASTNotEqual.java | 4 +-
.../org/apache/cayenne/exp/parser/ASTNotIn.java | 8 +-
.../apache/cayenne/exp/parser/ASTNotLike.java | 2 +-
.../exp/parser/ASTNotLikeIgnoreCase.java | 2 +-
.../org/apache/cayenne/exp/parser/ASTOr.java | 13 ++-
.../org/apache/cayenne/exp/parser/ASTTrue.java | 4 +-
.../cayenne/exp/parser/ConditionNode.java | 4 +-
.../apache/cayenne/exp/parser/Evaluator.java | 49 +++-----
.../parser/ExpressionEvaluateInMemoryTest.java | 63 ++++++++++
.../exp/parser/ExpressionEvaluationIT.java | 115 +++++++++++++++++--
24 files changed, 258 insertions(+), 85 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index b302a04..8491e23 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -507,7 +507,7 @@ public abstract class Expression implements Serializable, XMLSerializable {
@SuppressWarnings("unchecked")
public <T> List<T> filterObjects(Collection<T> objects) {
if (objects == null || objects.size() == 0) {
- return new LinkedList<T>(); // returning Collections.emptyList() could cause random client exceptions if they try to mutate the resulting list
+ return new LinkedList<>(); // returning Collections.emptyList() could cause random client exceptions if they try to mutate the resulting list
}
return (List<T>) filter(objects, new LinkedList<T>());
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
index 33fdcb1..e67957e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTAnd.java
@@ -74,13 +74,20 @@ public class ASTAnd extends AggregateConditionNode implements ValueInjector {
return Boolean.FALSE;
}
+ // https://en.wikipedia.org/wiki/Three-valued_logic
+ boolean unknown = false;
+ boolean result = true;
for (int i = 0; i < len; i++) {
- if (!ConversionUtil.toBoolean(evaluateChild(i, o))) {
- return Boolean.FALSE;
+ Object value = evaluateChild(i, o);
+ if (value == null) {
+ unknown = true;
+ } else if (!ConversionUtil.toBoolean(value)) {
+ result = false;
+ break;
}
}
- return Boolean.TRUE;
+ return result ? (unknown ? null : Boolean.TRUE) : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
index 0d65c84..ad4008f 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTBetween.java
@@ -53,7 +53,7 @@ public class ASTBetween extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o1 = evaluatedChildren[1];
Object o2 = evaluatedChildren[2];
Evaluator e = Evaluator.evaluator(o);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
index 3f313e5..f63a1ba 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEqual.java
@@ -64,9 +64,9 @@ public class ASTEqual extends ConditionNode implements ValueInjector {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
- return evaluateImpl(o, o2);
+ return evaluateImpl(o, o2) ? Boolean.TRUE : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
index 22145d0..5242764 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFalse.java
@@ -56,8 +56,8 @@ public class ASTFalse extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
- return false;
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ return Boolean.FALSE;
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
index 25f1af9..ca60fc2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreater.java
@@ -52,10 +52,13 @@ public class ASTGreater extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
Integer c = Evaluator.evaluator(o).compare(o, o2);
- return c != null && c > 0 ? Boolean.TRUE : Boolean.FALSE;
+ if(c == null) {
+ return null;
+ }
+ return c > 0 ? Boolean.TRUE : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
index fb93fa8..ee71273 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTGreaterOrEqual.java
@@ -54,10 +54,13 @@ public class ASTGreaterOrEqual extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
Integer c = Evaluator.evaluator(o).compare(o, o2);
- return c != null && c >= 0 ? Boolean.TRUE : Boolean.FALSE;
+ if(c == null) {
+ return null;
+ }
+ return c >= 0 ? Boolean.TRUE : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
index 31a4ac36..e91c5a3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
@@ -54,21 +54,22 @@ public class ASTIn extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
- // TODO: what if there's a NULL inside IN list?
- // e.g. ASTEqual evals as "NULL == NULL"
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
if (o == null || evaluatedChildren[1] == null) {
- return false;
+ // Even if there is NULL value in list we should return false,
+ // as check against NULL can be done only with IS NULL operator
+ // and moreover not all DB accept syntax like 'value IN (NULL)'
+ return Boolean.FALSE;
}
Object[] objects = (Object[]) evaluatedChildren[1];
for (Object object : objects) {
if (object != null && Evaluator.evaluator(o).eq(o, object)) {
- return true;
+ return Boolean.TRUE;
}
}
- return false;
+ return Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
index d8c75cc..81e5baf 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLess.java
@@ -54,10 +54,13 @@ public class ASTLess extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
Integer c = Evaluator.evaluator(o).compare(o, o2);
- return c != null && c < 0 ? Boolean.TRUE : Boolean.FALSE;
+ if(c == null) {
+ return null;
+ }
+ return c < 0 ? Boolean.TRUE : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
index 09d10cb..717e064 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLessOrEqual.java
@@ -54,10 +54,13 @@ public class ASTLessOrEqual extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
Integer c = Evaluator.evaluator(o).compare(o, o2);
- return c != null && c <= 0 ? Boolean.TRUE : Boolean.FALSE;
+ if(c == null) {
+ return null;
+ }
+ return c <= 0 ? Boolean.TRUE : Boolean.FALSE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
index 9d1eda6..bd45d9b 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLike.java
@@ -61,7 +61,7 @@ public class ASTLike extends PatternMatchNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
String s1 = ConversionUtil.toString(o);
if (s1 == null) {
return Boolean.FALSE;
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
index b0bd207..1cdc7f0 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTLikeIgnoreCase.java
@@ -62,7 +62,7 @@ public class ASTLikeIgnoreCase extends IgnoreCaseNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
String s1 = ConversionUtil.toString(o);
if (s1 == null) {
return Boolean.FALSE;
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
index 0c493e4..9973ccc 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNot.java
@@ -55,7 +55,12 @@ public class ASTNot extends AggregateConditionNode {
return Boolean.FALSE;
}
- return ConversionUtil.toBoolean(evaluateChild(0, o)) ? Boolean.FALSE : Boolean.TRUE;
+ Object o1 = evaluateChild(0, o);
+ if (o1 == null) {
+ return null;
+ }
+
+ return ConversionUtil.toBoolean(o1) ? Boolean.FALSE : Boolean.TRUE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
index 62743e3..8f35c99 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotBetween.java
@@ -48,7 +48,7 @@ public class ASTNotBetween extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o1 = evaluatedChildren[1];
Object o2 = evaluatedChildren[2];
Evaluator e = Evaluator.evaluator(o);
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
index 211b83e..0b7ffbe 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotEqual.java
@@ -51,9 +51,9 @@ public class ASTNotEqual extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
Object o2 = evaluatedChildren[1];
- return !ASTEqual.evaluateImpl(o, o2);
+ return ASTEqual.evaluateImpl(o, o2) ? Boolean.FALSE : Boolean.TRUE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
index 09cda00..09afa6d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
@@ -49,19 +49,19 @@ public class ASTNotIn extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
if (o == null || evaluatedChildren[1] == null) {
- return false;
+ return Boolean.FALSE;
}
Object[] objects = (Object[]) evaluatedChildren[1];
for (Object object : objects) {
if (object != null && Evaluator.evaluator(o).eq(o, object)) {
- return false;
+ return Boolean.FALSE;
}
}
- return true;
+ return Boolean.TRUE;
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
index ab90bc6..d01970c 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLike.java
@@ -57,7 +57,7 @@ public class ASTNotLike extends PatternMatchNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
String s1 = ConversionUtil.toString(o);
if (s1 == null) {
return Boolean.FALSE;
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
index b962fc3..7af54eb 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotLikeIgnoreCase.java
@@ -58,7 +58,7 @@ public class ASTNotLikeIgnoreCase extends IgnoreCaseNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
String s1 = ConversionUtil.toString(o);
if (s1 == null) {
return Boolean.FALSE;
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
index 1f6ef24..e1cfb5d 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTOr.java
@@ -68,13 +68,20 @@ public class ASTOr extends AggregateConditionNode {
return Boolean.FALSE;
}
+ // https://en.wikipedia.org/wiki/Three-valued_logic
+ boolean unknown = false;
+ boolean result = false;
for (int i = 0; i < len; i++) {
- if (ConversionUtil.toBoolean(evaluateChild(i, o))) {
- return Boolean.TRUE;
+ Object value = evaluateChild(i, o);
+ if (value == null) {
+ unknown = true;
+ } else if (ConversionUtil.toBoolean(value)) {
+ result = true;
+ break;
}
}
- return Boolean.FALSE;
+ return result ? Boolean.TRUE : (unknown ? null : Boolean.FALSE);
}
/**
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
index ae9de66..e305df3 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTTrue.java
@@ -52,8 +52,8 @@ public class ASTTrue extends ConditionNode {
}
@Override
- protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
- return true;
+ protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception {
+ return Boolean.TRUE;
}
@Override
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
index 7a05940..4759e70 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ConditionNode.java
@@ -74,7 +74,7 @@ public abstract class ConditionNode extends SimpleNode {
}
if (firstChild instanceof Collection) {
for(Object c : (Collection)firstChild) {
- if(evaluateSubNode(c, evaluatedChildren)) {
+ if(evaluateSubNode(c, evaluatedChildren) == Boolean.TRUE) {
return Boolean.TRUE;
}
}
@@ -86,5 +86,5 @@ public abstract class ConditionNode extends SimpleNode {
abstract protected int getRequiredChildrenCount();
- abstract protected boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception;
+ abstract protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) throws Exception;
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
index df6785b..8b1b619 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/Evaluator.java
@@ -47,9 +47,9 @@ abstract class Evaluator {
private static final Evaluator PERSISTENT_EVALUATOR;
private static final Evaluator BIG_DECIMAL_EVALUATOR;
private static final Evaluator NUMBER_EVALUATOR;
- private static final Evaluator COMPAREABLE_EVALUATOR;
+ private static final Evaluator COMPARABLE_EVALUATOR;
- static double EPSILON = 0.0000001;
+ private static final double EPSILON = 0.0000001;
/**
* A decorator of an evaluator that presumes non-null 'lhs' argument and
@@ -64,22 +64,20 @@ abstract class Evaluator {
@Override
Integer compare(Object lhs, Object rhs) {
-
if (rhs == null) {
- return 1;
- } else {
- return delegate.compare(lhs, rhs);
+ return null;
}
+ return delegate.compare(lhs, rhs);
}
@Override
boolean eq(Object lhs, Object rhs) {
- return rhs == null ? false : delegate.eq(lhs, rhs);
+ return rhs != null && delegate.eq(lhs, rhs);
}
}
static {
- evaluators = new ConcurrentHashMap<Class<?>, Evaluator>();
+ evaluators = new ConcurrentHashMap<>();
NULL_LHS_EVALUATOR = new Evaluator() {
@Override
@@ -159,12 +157,9 @@ abstract class Evaluator {
@Override
boolean eq(Object lhs, Object rhs) {
-
- // BigDecimals must be compared using compareTo (
- // see CAY-280 and BigDecimal.equals JavaDoc)
-
+ // BigDecimals must be compared using compareTo (see CAY-280 and BigDecimal.equals JavaDoc)
Integer c = compare(lhs, rhs);
- return c != null && c == 0;
+ return c == 0;
}
});
@@ -178,19 +173,14 @@ abstract class Evaluator {
@Override
boolean eq(Object lhs, Object rhs) {
- return equalNumbers((Number)lhs, (Number)rhs);
+ return equalNumbers((Number)lhs, rhs);
}
private final List WHOLE_NUMBER_CLASSES = Arrays.asList(Byte.class, Short.class, Integer.class, AtomicInteger.class, Long.class, AtomicLong.class, BigInteger.class);
- private final List FLOATING_POINT_CLASSES = Arrays.asList(Float.class, Double.class);
/**
* Returns the widest primitive wrapper class given the two operands, used in preparation for
* comparing two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
- *
- * @param lhs
- * @param rhs
- * @return
*/
Class<?> widestNumberType(Number lhs, Number rhs) {
if (lhs.getClass().equals(rhs.getClass())) return lhs.getClass();
@@ -201,15 +191,8 @@ abstract class Evaluator {
Class<?> widestClass;
if (lhsIndex != -1 && rhsIndex != -1) {
widestClass = (Class<?>) WHOLE_NUMBER_CLASSES.get(Math.max(lhsIndex, rhsIndex));
-
- } else if (lhsIndex == -1 && rhsIndex == -1) {
- widestClass = Double.class; // must be one float and one double;
-
- } else if (lhsIndex != -1 || rhsIndex != -1){ // must be whole number and a float or double
- widestClass = Double.class;
-
} else {
- widestClass = null;
+ widestClass = Double.class;
}
return widestClass;
@@ -217,9 +200,6 @@ abstract class Evaluator {
/**
* Enables equality tests for two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
- * @param lhs
- * @param _rhs
- * @return
*/
boolean equalNumbers(Number lhs, Object _rhs) {
if (!Number.class.isAssignableFrom(_rhs.getClass())) {
@@ -252,9 +232,6 @@ abstract class Evaluator {
/**
* Enables comparison of two boxed numbers of different types, like java.lang.Short and java.lang.Integer.
- * @param lhs
- * @param _rhs
- * @return
*/
Integer compareNumbers(Number lhs, Object _rhs) {
if (!Number.class.isAssignableFrom(_rhs.getClass())) {
@@ -299,7 +276,7 @@ abstract class Evaluator {
}
});
- COMPAREABLE_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() {
+ COMPARABLE_EVALUATOR = new NonNullLhsEvaluator(new Evaluator() {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
@@ -314,7 +291,7 @@ abstract class Evaluator {
});
}
- static <T> Evaluator evaluator(Object lhs) {
+ static Evaluator evaluator(Object lhs) {
if (lhs == null) {
return NULL_LHS_EVALUATOR;
@@ -354,7 +331,7 @@ abstract class Evaluator {
}
if (Comparable.class.isAssignableFrom(lhsType)) {
- return COMPAREABLE_EVALUATOR;
+ return COMPARABLE_EVALUATOR;
}
// nothing we recognize... return default
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java
index 25afcf0..1d9e826 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluateInMemoryTest.java
@@ -18,11 +18,15 @@
****************************************************************/
package org.apache.cayenne.exp.parser;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;
import java.math.BigDecimal;
import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.testdo.testmap.Artist;
import org.junit.Test;
// TODO: split it between AST* unit tests (partially done already)
@@ -68,4 +72,63 @@ public class ExpressionEvaluateInMemoryTest {
assertEquals(Boolean.FALSE, new ASTFalse().evaluate(null));
}
+ @Test
+ public void testEvaluateNullCompare() throws Exception {
+ Expression expression = new ASTGreater(new ASTObjPath("artistName"), "A");
+ assertFalse(expression.match(new Artist()));
+ assertFalse(expression.notExp().match(new Artist()));
+ }
+
+ @Test
+ public void testEvaluateCompareNull() throws Exception {
+ Artist a1 = new Artist();
+ a1.setArtistName("Name");
+ Expression expression = new ASTGreater(new ASTObjPath("artistName"), null);
+ assertFalse(expression.match(a1));
+ assertFalse(expression.notExp().match(a1));
+ a1.setSomeOtherObjectProperty(new BigDecimal(1));
+ expression = ExpressionFactory.exp("someOtherObjectProperty > null");
+ assertFalse(expression.match(a1));
+ }
+
+ @Test
+ public void testEvaluateEqualsNull() throws Exception {
+ Artist a1 = new Artist();
+ Expression isNull = Artist.ARTIST_NAME.isNull();
+ assertTrue(isNull.match(a1));
+ assertFalse(isNull.notExp().match(a1));
+ }
+
+ @Test
+ public void testEvaluateNotEqualsNullColumn() throws Exception {
+ Expression notEquals = ExpressionFactory.exp("artistName <> someOtherProperty");
+ assertFalse(notEquals.match(new Artist()));
+ assertTrue(notEquals.notExp().match(new Artist()));
+ }
+
+ @Test
+ public void testNullAnd() {
+ Expression nullExp = ExpressionFactory.exp("null > 0");
+
+ ASTAnd nullAndTrue = new ASTAnd(new Object[] {nullExp, new ASTTrue()});
+ assertFalse(nullAndTrue.match(null));
+ assertFalse(nullAndTrue.notExp().match(null));
+
+ ASTAnd nullAndFalse = new ASTAnd(new Object[] {nullExp, new ASTFalse()});
+ assertFalse(nullAndFalse.match(null));
+ assertTrue(nullAndFalse.notExp().match(null));
+ }
+
+ @Test
+ public void testNullOr() {
+ Expression nullExp = ExpressionFactory.exp("null > 0");
+
+ ASTOr nullOrTrue = new ASTOr(new Object[] {nullExp, new ASTTrue()});
+ assertTrue(nullOrTrue.match(null));
+ assertFalse(nullOrTrue.notExp().match(null));
+
+ ASTOr nullOrFalse = new ASTOr(new Object[] {nullExp, new ASTFalse()});
+ assertFalse(nullOrFalse.match(null));
+ assertFalse(nullOrFalse.notExp().match(null));
+ }
}
http://git-wip-us.apache.org/repos/asf/cayenne/blob/8e43461e/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
index e33dba3..d8bc19d 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/exp/parser/ExpressionEvaluationIT.java
@@ -36,7 +36,6 @@ import org.apache.cayenne.unit.di.server.CayenneProjects;
import org.apache.cayenne.unit.di.server.ServerCase;
import org.apache.cayenne.unit.di.server.UseServerRuntime;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
@@ -45,8 +44,9 @@ import static org.junit.Assert.assertEquals;
* Here we compare Expression evaluation in-memory vs execution in database.
* Results should be same for both cases.
* Here is primary collection of complex expressions:
- * - To-Many relationships
- * - Nulls
+ * - To-Many relationships comparisons
+ * - Null comparisons
+ * - Null in AND and OR expressions
*
* @since 4.0
*/
@@ -82,6 +82,7 @@ public class ExpressionEvaluationIT extends ServerCase {
}
tPaintings.insert(11, "painting11", null, 1, 10000);
+ tPaintings.insert(12, "painting12", 1, 1, null);
}
@Test
@@ -246,16 +247,116 @@ public class ExpressionEvaluationIT extends ServerCase {
}
@Test
- @Ignore("Null comparisons are partially broken for now")
- public void testNull() throws Exception {
+ public void testCollectionWithNull() {
+ Expression exp = Artist.PAINTING_ARRAY.dot(Painting.ESTIMATED_PRICE)
+ .lt(new BigDecimal(200));
+
+ compareSqlAndEval(exp, 1);
+ }
+
+ @Test
+ public void testGreaterWithNull() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+
+ Expression expression = Artist.DATE_OF_BIRTH
+ .gt(new java.sql.Date(System.currentTimeMillis()));
+
+ compareSqlAndEval(expression, 0);
+
+ Expression expression1 = expression.notExp();
+ compareSqlAndEval(expression1, 0);
+ }
+
+ @Test
+ public void testGreaterEqualWithNull() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+
+ Expression expression = Artist.DATE_OF_BIRTH
+ .gte(new java.sql.Date(System.currentTimeMillis()));
+
+ compareSqlAndEval(expression, 0);
+
+ Expression expression1 = expression.notExp();
+ compareSqlAndEval(expression1, 0);
+ }
+
+ @Test
+ public void testLessWithNull() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+
+ Expression expression = Artist.DATE_OF_BIRTH
+ .lt(new java.sql.Date(System.currentTimeMillis()));
+
+ compareSqlAndEval(expression, 0);
+
+ Expression expression1 = expression.notExp();
+ compareSqlAndEval(expression1, 0);
+ }
+
+ @Test
+ public void testLessEqualWithNull() throws Exception {
+ tPaintings.deleteAll();
tArtist.deleteAll();
tArtist.insert(7, "artist7", null);
- Expression expression = Artist.DATE_OF_BIRTH.gt(new java.sql.Date(System.currentTimeMillis()));
+ Expression expression = Artist.DATE_OF_BIRTH
+ .lte(new java.sql.Date(System.currentTimeMillis()));
+
compareSqlAndEval(expression, 0);
Expression expression1 = expression.notExp();
- compareSqlAndEval(expression1, 0); // this one will fail, in-memory filter will match one Artist
+ compareSqlAndEval(expression1, 0);
+ }
+
+ @Test
+ public void testAndWithNull() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+ tArtist.insert(8, "artist8", null);
+ tArtist.insert(9, "artist9", null);
+
+ Expression nullExp = Artist.DATE_OF_BIRTH.lt(new java.sql.Date(System.currentTimeMillis()));
+ Expression and = ExpressionFactory.and(nullExp, Artist.ARTIST_NAME.eq("artist7"));
+
+ compareSqlAndEval(and, 0);
+ compareSqlAndEval(and.notExp(), 2);
+ }
+
+ @Test
+ public void testAndWithNull2() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+ tArtist.insert(8, "artist8", null);
+ tArtist.insert(9, "artist9", null);
+
+ Expression nullExp = Artist.DATE_OF_BIRTH.lt(new java.sql.Date(System.currentTimeMillis()));
+ Expression and = ExpressionFactory.and(nullExp, Artist.ARTIST_NAME.eq("artist10"));
+
+ compareSqlAndEval(and, 0);
+ compareSqlAndEval(and.notExp(), 3);
+ }
+
+ @Test
+ public void testOrWithNull() throws Exception {
+ tPaintings.deleteAll();
+ tArtist.deleteAll();
+ tArtist.insert(7, "artist7", null);
+ tArtist.insert(8, "artist8", null);
+ tArtist.insert(9, "artist9", null);
+
+ Expression nullExp = Artist.DATE_OF_BIRTH.lt(new java.sql.Date(System.currentTimeMillis()));
+ Expression and = ExpressionFactory.or(nullExp, Artist.ARTIST_NAME.eq("artist7"));
+
+ compareSqlAndEval(and, 1);
+ compareSqlAndEval(and.notExp(), 0);
}
private void compareSqlAndEval(Expression exp, int expectedCount) {