#1643 - ENH: Add expression inOrEmpty() ... as a convenience for conditionally added a IN expression

This commit is contained in:
rob bygrave
2019-03-01 18:24:00 +13:00
parent 888384f7db
commit a4698ea1b6
9 changed files with 301 additions and 21 deletions
+37
View File
@@ -250,6 +250,43 @@ public class Expr {
return Ebean.getExpressionFactory().in(propertyName, values);
}
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
* <p>
* Without this we typically need to code an <code>if</code> block to only add
* the IN predicate if the collection is not empty like:
* </p>
*
* <h3>Without inOrEmpty()</h3>
* <pre>{@code
*
* query.where() // add some predicates
* .eq("status", Status.NEW);
*
* if (ids != null && !ids.isEmpty()) {
* query.where().in("customer.id", ids);
* }
*
* query.findList();
*
* }</pre>
*
* <h3>Using inOrEmpty()</h3>
* <pre>{@code
*
* query.where()
* .eq("status", Status.NEW)
* .inOrEmpty("customer.id", ids)
* .findList();
*
* }</pre>
*/
public static Expression inOrEmpty(String propertyName, Collection<?> values) {
return Ebean.getExpressionFactory().inOrEmpty(propertyName, values);
}
/**
* Id Equal to - ID property is equal to the value.
*/
@@ -325,6 +325,41 @@ public interface ExpressionFactory {
*/
Expression in(String propertyName, Collection<?> values);
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
* <p>
* Without this we typically need to code an <code>if</code> block to only add
* the IN predicate if the collection is not empty like:
* </p>
*
* <h3>Without inOrEmpty()</h3>
* <pre>{@code
*
* query.where() // add some predicates
* .eq("status", Status.NEW);
*
* if (ids != null && !ids.isEmpty()) {
* query.where().in("customer.id", ids);
* }
*
* query.findList();
*
* }</pre>
*
* <h3>Using inOrEmpty()</h3>
* <pre>{@code
*
* query.where()
* .eq("status", Status.NEW)
* .inOrEmpty("customer.id", ids)
* .findList();
*
* }</pre>
*/
Expression inOrEmpty(String propertyName, Collection<?> values);
/**
* Not In - property has a value in the array of values.
*/
@@ -1003,6 +1003,41 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> in(String propertyName, Collection<?> values);
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
* <p>
* Without this we typically need to code an <code>if</code> block to only add
* the IN predicate if the collection is not empty like:
* </p>
*
* <h3>Without inOrEmpty()</h3>
* <pre>{@code
*
* query.where() // add some predicates
* .eq("status", Status.NEW);
*
* if (ids != null && !ids.isEmpty()) {
* query.where().in("customer.id", ids);
* }
*
* query.findList();
*
* }</pre>
*
* <h3>Using inOrEmpty()</h3>
* <pre>{@code
*
* query.where()
* .eq("status", Status.NEW)
* .inOrEmpty("customer.id", ids)
* .findList();
*
* }</pre>
*/
ExpressionList<T> inOrEmpty(String propertyName, Collection<?> values);
/**
* In - using a subQuery.
* <p>
@@ -455,6 +455,16 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new InExpression(propertyName, values, false);
}
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
*/
@Override
public Expression inOrEmpty(String propertyName, Collection<?> values) {
return new InExpression(propertyName, values, false, true);
}
/**
* In - property has a value in the array of values.
*/
@@ -912,6 +912,12 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return this;
}
@Override
public ExpressionList<T> inOrEmpty(String propertyName, Collection<?> values) {
add(expr.inOrEmpty(propertyName, values));
return this;
}
@Override
public ExpressionList<T> in(String propertyName, Object... values) {
add(expr.in(propertyName, values));
@@ -17,8 +17,16 @@ import java.util.List;
class InExpression extends AbstractExpression {
private static final String SQL_TRUE = "1=1";
private static final String SQL_FALSE = "1=0";
private final boolean not;
/**
* Set to true when adding "1=1" predicate (due to null or empty sourceValues).
*/
private final boolean empty;
private final Collection<?> sourceValues;
private List<Object> bindValues;
@@ -26,18 +34,27 @@ class InExpression extends AbstractExpression {
private boolean multiValueSupported;
InExpression(String propertyName, Collection<?> sourceValues, boolean not) {
this(propertyName, sourceValues, not, false);
}
InExpression(String propertyName, Collection<?> sourceValues, boolean not, boolean orEmpty) {
super(propertyName);
this.sourceValues = sourceValues;
this.not = not;
this.empty = orEmpty && (sourceValues == null || sourceValues.isEmpty());
}
InExpression(String propertyName, Object[] array, boolean not) {
super(propertyName);
this.sourceValues = Arrays.asList(array);
this.not = not;
this.empty = false;
}
private List<Object> values() {
if (empty || sourceValues == null) {
return Collections.emptyList();
}
List<Object> vals = new ArrayList<>(sourceValues.size());
for (Object sourceValue : sourceValues) {
assert sourceValue != null : "null is not allowed in in-queries";
@@ -48,8 +65,8 @@ class InExpression extends AbstractExpression {
@Override
public boolean naturalKey(NaturalKeyQueryData<?> data) {
// can't use naturalKey cache for NOT IN
if (not) {
// can't use naturalKey cache for NOT IN or when "empty"
if (not || empty) {
return false;
}
List<Object> copy = data.matchIn(propName, bindValues);
@@ -70,11 +87,16 @@ class InExpression extends AbstractExpression {
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
context.writeIn(propName, values().toArray(), not);
if (!empty) {
context.writeIn(propName, values().toArray(), not);
}
}
@Override
public void addBindValues(SpiExpressionRequest request) {
if (empty) {
return;
}
for (Object value : bindValues) {
if (value == null) {
throw new NullPointerException("null values in 'in(...)' queries must be handled separately!");
@@ -108,10 +130,12 @@ class InExpression extends AbstractExpression {
@Override
public void addSql(SpiExpressionRequest request) {
if (empty) {
request.append(SQL_TRUE);
return;
}
if (bindValues.isEmpty()) {
String expr = not ? "1=1" : "1=0";
request.append(expr);
request.append(not ? SQL_TRUE : SQL_FALSE);
return;
}
@@ -142,10 +166,14 @@ class InExpression extends AbstractExpression {
builder.append("In[");
}
builder.append(propName);
builder.append(" ?");
if (!multiValueSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(bindValues.size());
if (empty) {
builder.append("empty");
} else {
builder.append(" ?");
if (!multiValueSupported) {
// query plan specific to the number of parameters in the IN clause
builder.append(bindValues.size());
}
}
builder.append("]");
}
@@ -672,6 +672,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.in(propertyName, values);
}
@Override
public ExpressionList<T> inOrEmpty(String propertyName, Collection<?> values) {
return exprList.inOrEmpty(propertyName, values);
}
@Override
public ExpressionList<T> in(String propertyName, Object... values) {
return exprList.in(propertyName, values);