#2768 - ENH: Add eqIfPresent() - a helper expression for fluid style when some EQ are optional

This commit is contained in:
Rob Bygrave
2022-08-01 16:36:09 +12:00
parent f930471beb
commit 7474430a63
6 changed files with 116 additions and 3 deletions
@@ -385,7 +385,7 @@ public interface ExpressionList<T> {
/**
* Executes the query returning a set of values for a single property.
*
* <p>
* This can be used to cache sets.
*
* @return a HashSet of values for the selegted property
@@ -835,6 +835,20 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> eq(String propertyName, Object value);
/**
* Is EQUAL TO if value is non-null and otherwise no expression is added to the query.
* <p>
* This is the EQUAL TO equivalent to {@link #inOrEmpty(String, Collection)} where the expression/predicate
* is only added when the value is non-null.
* <p>
* This is effectively a helper method that allows a query to be built in fluid style where some predicates are
* effectively optional. We can use <code>eqIfPresent()</code> rather than having a separate if block.
* <p>
* Another option is to instead globally use {@link io.ebean.config.DatabaseConfig#setExpressionEqualsWithNullAsNoop(boolean)}
* but that is not always desirable.
*/
ExpressionList<T> eqIfPresent(String propertyName, @Nullable Object value);
/**
* Equal To or Null - property is equal to a given value or null.
*/
@@ -798,6 +798,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return add(expr.eq(propertyName, value));
}
@Override
public ExpressionList<T> eqIfPresent(String propertyName, @Nullable Object value) {
return value == null ? this : add(expr.eq(propertyName, value));
}
@Override
public ExpressionList<T> eqOrNull(String propertyName, Object value) {
return add(expr.eqOrNull(propertyName, value));
@@ -309,6 +309,11 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
return exprList.eq(propertyName, value);
}
@Override
public ExpressionList<T> eqIfPresent(String propertyName, @Nullable Object value) {
return value == null ? this : exprList.eq(propertyName, value);
}
@Override
public ExpressionList<T> eqOrNull(String propertyName, Object value) {
return exprList.eqOrNull(propertyName, value);
@@ -1,5 +1,6 @@
package io.ebean.typequery;
import io.avaje.lang.Nullable;
import io.ebean.Query;
import java.util.Collection;
@@ -82,6 +83,25 @@ public abstract class PBaseValueEqual<R, T> extends TQPropertyBase<R> {
return _root;
}
/**
* Is equal to if value is non-null and otherwise no expression is added to the query.
* <p>
* That is, only add the EQUAL TO predicate if the value is not null.
* <p>
* This is the EQUAL TO equivalent to {@link #inOrEmpty(Collection)} where the expression/predicate
* is only added to the query when the value is non-null.
* <p>
* This is effectively a helper method that allows a query to be built in fluid style where some predicates are
* effectively optional. We can use <code>eqIfPresent()</code> rather than having a separate if block.
*
* @param value the equal to bind value
* @return the root query bean instance
*/
public final R eqIfPresent(@Nullable T value) {
expr().eqIfPresent(_name, value);
return _root;
}
/**
* Is equal to or Null.
*
@@ -247,7 +267,7 @@ public abstract class PBaseValueEqual<R, T> extends TQPropertyBase<R> {
public final R isIn(Query<?> subQuery) {
return in(subQuery);
}
/**
* Is NOT in the result of a subquery.
*
@@ -1,17 +1,20 @@
package org.querytest;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import org.example.domain.MyInner;
import org.example.domain.query.QMyInner;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
class MyInnerTest {
@Test
void insert_and_find() {
MyInner myInner = new MyInner()
.id(42).one("one").description("foo");
@@ -26,5 +29,25 @@ class MyInnerTest {
.findOne();
assertThat(found2).isNotNull();
LoggedSql.start();
MyInner found3 = new QMyInner()
.one.eqIfPresent("one")
.findOne();
assertThat(found3).isNotNull();
MyInner found4 = new QMyInner()
.one.eqIfPresent(null)
.description.eqIfPresent("foo")
.findOne();
assertThat(found4).isNotNull();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.one = ?;");
assertThat(sql.get(1)).contains("select t0.id, t0.one, t0.id, t0.one, t0.description from my_inner t0 where t0.description = ?;");
}
}
@@ -23,6 +23,52 @@ public class TestQueryIsNull extends BaseTestCase {
assertThat(query.getGeneratedSql()).contains("name is null");
}
@Test
void query_defaultNullToIsNull() {
ResetBasicData.reset();
Query<Order> query = DB.find(Order.class).select("id, status")
.where().eq("status", null).query();
query.findList();
assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.status is null");
}
@Test
void query_eqIfPresent() {
ResetBasicData.reset();
Query<Order> query = DB.find(Order.class).select("id, status")
.where().eqIfPresent("status", null).query();
query.findList();
assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0");
Query<Order> query1 = DB.find(Order.class).select("id, status")
.where().eqIfPresent("status", null).isNull("shipDate").query();
query1.findList();
assertThat(query1.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.ship_date is null");
Query<Order> query2 = DB.find(Order.class).select("id, status")
.where().isNotNull("orderDate").eqIfPresent("status", null).query();
query2.findList();
assertThat(query2.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null");
Query<Order> query3 = DB.find(Order.class).select("id, status")
.where().isNotNull("orderDate").eqIfPresent("status", null).isNull("shipDate").query();
query3.findList();
assertThat(query3.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null and t0.ship_date is null");
Query<Order> query4 = DB.find(Order.class).select("id, status")
.where().isNotNull("orderDate").eqIfPresent("status", Order.Status.NEW).isNull("shipDate").query();
query4.findList();
assertThat(query4.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.order_date is not null and t0.status = ? and t0.ship_date is null");
}
@Test
public void isNotNull_when_OneToMany_expect_existsSubquery() {
ResetBasicData.reset();