From 54f3dbf35aec296040e8ba28c41021d827b93fbb Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Thu, 30 Jul 2015 06:41:51 +1200 Subject: [PATCH] #361 - ENH: Add ServerConfig expressionEqualsWithNullAsNoop - Can eq ne gt ... methods in ExpressionList treat null value as 1=1 ? --- .../com/avaje/ebean/config/ServerConfig.java | 25 ++++++++ .../server/core/InternalConfiguration.java | 2 +- .../expression/DefaultExpressionFactory.java | 10 ++-- .../server/expression/NoopExpression.java | 47 +++++++++++++++ .../DefaultExpressionFactoryTest.java | 58 +++++++++++++++++++ .../expression/NoopExpressionQueryTest.java | 42 ++++++++++++++ .../server/querydefn/TestQueryLanguage.java | 2 +- 7 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java create mode 100644 src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java diff --git a/src/main/java/com/avaje/ebean/config/ServerConfig.java b/src/main/java/com/avaje/ebean/config/ServerConfig.java index 0e7b7f51a..aab402ac3 100644 --- a/src/main/java/com/avaje/ebean/config/ServerConfig.java +++ b/src/main/java/com/avaje/ebean/config/ServerConfig.java @@ -324,6 +324,11 @@ public class ServerConfig { private Object objectMapper; private boolean diffFlatMode; + /** + * Set to true if you want eq("someProperty", null) to generate 1=1 rather than "is null" sql expression. + */ + private boolean expressionEqualsWithNullAsNoop; + /** * Construct a Server Configuration for programmatically creating an EbeanServer. */ @@ -1902,6 +1907,7 @@ public class ServerConfig { persistenceContextScope = PersistenceContextScope.valueOf(p.get("persistenceContextScope", "TRANSACTION")); + expressionEqualsWithNullAsNoop = p.getBoolean("expressionEqualsWithNullAsNoop", expressionEqualsWithNullAsNoop); diffFlatMode = p.getBoolean("diffFlatMode", diffFlatMode); asOfViewSuffix = p.get("asOfViewSuffix", asOfViewSuffix); asOfSysPeriod = p.get("asOfSysPeriod", asOfSysPeriod); @@ -2029,4 +2035,23 @@ public class ServerConfig { public void setDiffFlatMode(boolean diffFlatMode) { this.diffFlatMode = diffFlatMode; } + + /** + * Return true if eq("someProperty", null) should to generate "1=1" rather than "is null" sql expression. + */ + public boolean isExpressionEqualsWithNullAsNoop() { + return expressionEqualsWithNullAsNoop; + } + + /** + * Set to true if you want eq("someProperty", null) to generate "1=1" rather than "is null" sql expression. + *

+ * Setting this to true has the effect that eq(propertyName, value), ieq(propertyName, value) and + * ne(propertyName, value) have no effect when the value is null. The expression factory adds a NoopExpression + * which will add "1=1" into the SQL rather than "is null". + *

+ */ + public void setExpressionEqualsWithNullAsNoop(boolean expressionEqualsWithNullAsNoop) { + this.expressionEqualsWithNullAsNoop = expressionEqualsWithNullAsNoop; + } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java index c751d53de..ec6f5649b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java @@ -106,7 +106,7 @@ public class InternalConfiguration { this.cacheManager = cacheManager; this.serverConfig = serverConfig; this.bootupClasses = bootupClasses; - this.expressionFactory = new DefaultExpressionFactory(); + this.expressionFactory = new DefaultExpressionFactory(serverConfig.isExpressionEqualsWithNullAsNoop()); this.typeManager = new DefaultTypeManager(serverConfig, bootupClasses); this.binder = new Binder(typeManager); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java index ceea64c4c..42f8f7e05 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -22,8 +22,10 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { private static final Object[] EMPTY_ARRAY = new Object[] {}; + private final boolean equalsWithNullAsNoop; - public DefaultExpressionFactory() { + public DefaultExpressionFactory(boolean equalsWithNullAsNoop) { + this.equalsWithNullAsNoop = equalsWithNullAsNoop; } public ExpressionFactory createExpressionFactory(){ @@ -39,7 +41,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ public Expression eq(String propertyName, Object value) { if (value == null) { - return isNull(propertyName); + return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName); } return new SimpleExpression(propertyName, SimpleExpression.Op.EQ, value); } @@ -49,7 +51,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ public Expression ne(String propertyName, Object value) { if (value == null) { - return isNotNull(propertyName); + return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNotNull(propertyName); } return new SimpleExpression(propertyName, SimpleExpression.Op.NOT_EQ, value); } @@ -60,7 +62,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ public Expression ieq(String propertyName, String value) { if (value == null) { - return isNull(propertyName); + return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName); } return new CaseInsensitiveEqualExpression(propertyName, value); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java new file mode 100644 index 000000000..64dd12f8d --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java @@ -0,0 +1,47 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.event.BeanQueryRequest; +import com.avaje.ebeaninternal.api.HashQueryPlanBuilder; +import com.avaje.ebeaninternal.api.ManyWhereJoins; +import com.avaje.ebeaninternal.api.SpiExpression; +import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; + +/** + * Effectively an expression that has no effect. + */ +class NoopExpression implements SpiExpression { + + protected static final NoopExpression INSTANCE = new NoopExpression(); + + @Override + public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { + // nothing to do + } + + @Override + public void queryAutoFetchHash(HashQueryPlanBuilder builder) { + builder.add(NoopExpression.class); + } + + @Override + public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + queryAutoFetchHash(builder); + } + + @Override + public int queryBindHash() { + // no bind values + return 0; + } + + @Override + public void addSql(SpiExpressionRequest request) { + request.append("1=1"); + } + + @Override + public void addBindValues(SpiExpressionRequest request) { + // nothing to do + } +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java new file mode 100644 index 000000000..0b048117a --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java @@ -0,0 +1,58 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.Expression; +import org.junit.Test; + +import static org.assertj.core.api.StrictAssertions.assertThat; + + +public class DefaultExpressionFactoryTest { + + @Test + public void testEq() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(false); + Expression expression = factory.eq("name", null); + assertThat(expression).isInstanceOf(NullExpression.class); + } + + @Test + public void testNe() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(false); + Expression expression = factory.ne("name", null); + assertThat(expression).isInstanceOf(NullExpression.class); + } + + @Test + public void testIeq() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(false); + Expression expression = factory.ieq("name", null); + assertThat(expression).isInstanceOf(NullExpression.class); + } + + @Test + public void testEq_with_equalsWithNullAsNoop() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(true); + Expression expression = factory.eq("name", null); + assertThat(expression).isInstanceOf(NoopExpression.class); + } + + @Test + public void testNe_with_equalsWithNullAsNoop() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(true); + Expression expression = factory.ne("name", null); + assertThat(expression).isInstanceOf(NoopExpression.class); + } + + @Test + public void testIeq_with_equalsWithNullAsNoop() throws Exception { + + DefaultExpressionFactory factory = new DefaultExpressionFactory(true); + Expression expression = factory.ieq("name", null); + assertThat(expression).isInstanceOf(NoopExpression.class); + } +} \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java new file mode 100644 index 000000000..9a42ea729 --- /dev/null +++ b/src/test/java/com/avaje/ebeaninternal/server/expression/NoopExpressionQueryTest.java @@ -0,0 +1,42 @@ +package com.avaje.ebeaninternal.server.expression; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.Query; +import com.avaje.tests.model.basic.Customer; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class NoopExpressionQueryTest extends BaseTestCase { + + @Test + public void test() { + + Query query = Ebean.find(Customer.class) + .select("id") + .where().add(NoopExpression.INSTANCE) + .query(); + + query.findList(); + String generatedSql = query.getGeneratedSql(); + + assertThat(generatedSql).contains("select t0.id c0 from o_customer t0 where 1=1"); + } + + @Test + public void test_withPreAndPost() { + + Query query = Ebean.find(Customer.class) + .select("id") + .where().eq("name", null) + .add(NoopExpression.INSTANCE) + .ne("status", null) + .query(); + + query.findList(); + String generatedSql = query.getGeneratedSql(); + + assertThat(generatedSql).contains("select t0.id c0 from o_customer t0 where t0.name is null and 1=1 and t0.status is not null"); + } +} diff --git a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java index 9706fa4fc..fb0cc9a26 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java +++ b/src/test/java/com/avaje/ebeaninternal/server/querydefn/TestQueryLanguage.java @@ -87,7 +87,7 @@ public class TestQueryLanguage extends BaseTestCase { OrmQueryDetailParser p = new OrmQueryDetailParser(q); p.parse(); DefaultOrmQuery qry = new DefaultOrmQuery(Order.class, server, - new DefaultExpressionFactory(), (String) null); + new DefaultExpressionFactory(false), (String) null); p.assign(qry); return qry;