Compare commits

..
Author SHA1 Message Date
Rob Bygrave 78817bca73 #3666 Fix for NPE with filterMany() containing or()
For FilterMany with QueryBeans, it creates a queryBean to build the filterMany predicates with. This query bean needed 2 changes for this fix:

1. Needs to set it's internal "root" such that it supports chaining (required for or() etc)
2. The ExpressionFactory needs to be explicitly passed to the expression list (rather than get it from the query which is actually null in this case).
2025-08-30 09:29:25 +12:00
6 changed files with 28 additions and 18 deletions
@@ -726,7 +726,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> conjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.AND, query, query.where());
return new JunctionExpression<>(Junction.Type.AND, query, this, query.where());
}
/**
@@ -734,7 +734,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> disjunction(Query<T> query) {
return new JunctionExpression<>(Junction.Type.OR, query, query.where());
return new JunctionExpression<>(Junction.Type.OR, query, this, query.where());
}
/**
@@ -742,7 +742,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> conjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.AND, query, parent);
return new JunctionExpression<>(Junction.Type.AND, query, this, parent);
}
/**
@@ -750,14 +750,14 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(Junction.Type.OR, query, parent);
return new JunctionExpression<>(Junction.Type.OR, query, this, parent);
}
/**
* Return a list of expressions that are wrapped by NOT.
*/
public <T> Junction<T> junction(Junction.Type type, Query<T> query) {
return new JunctionExpression<>(type, query, query.where());
return new JunctionExpression<>(type, query, this, query.where());
}
/**
@@ -765,6 +765,6 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
*/
@Override
public <T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
return new JunctionExpression<>(type, query, parent);
return new JunctionExpression<>(type, query, this, parent);
}
}
@@ -40,12 +40,8 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
/**
* Construct for Text root expression list - this handles implicit Bool Should, Must etc.
*/
public DefaultExpressionList(Query<T> query) {
this(query, query.getExpressionFactory(), null, new ArrayList<>(), true);
}
public DefaultExpressionList(Query<T> query, ExpressionList<T> parentExprList) {
this(query, query.getExpressionFactory(), parentExprList, new ArrayList<>());
public DefaultExpressionList(Query<T> query, boolean textRoot) {
this(query, query.getExpressionFactory(), null, new ArrayList<>(), textRoot);
}
DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list) {
@@ -24,9 +24,9 @@ final class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expr
DefaultExpressionList<T> exprList;
Junction.Type type;
JunctionExpression(Junction.Type type, Query<T> query, ExpressionList<T> parent) {
JunctionExpression(Junction.Type type, Query<T> query, ExpressionFactory expr, ExpressionList<T> parent) {
this.type = type;
this.exprList = new DefaultExpressionList<>(query, parent);
this.exprList = new DefaultExpressionList<>(query, expr, parent, new ArrayList<>());
}
/**
@@ -1895,7 +1895,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
public final ExpressionList<T> text() {
if (textExpressions == null) {
useDocStore = true;
textExpressions = new DefaultExpressionList<>(this);
textExpressions = new DefaultExpressionList<>(this, true);
}
return textExpressions;
}
@@ -1903,7 +1903,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final ExpressionList<T> where() {
if (whereExpressions == null) {
whereExpressions = new DefaultExpressionList<>(this, null);
whereExpressions = new DefaultExpressionList<>(this, false);
}
return whereExpressions;
}
@@ -1924,7 +1924,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final ExpressionList<T> having() {
if (havingExpressions == null) {
havingExpressions = new DefaultExpressionList<>(this, null);
havingExpressions = new DefaultExpressionList<>(this, false);
}
return havingExpressions;
}
@@ -121,7 +121,7 @@ public abstract class QueryBean<T, R extends QueryBean<T, R>> implements IQueryB
/** Construct for FilterMany */
protected QueryBean(ExpressionList<T> filter) {
this.query = null;
this.root = null;
this.root = (R) this;
this.whereStack = new ArrayStack<>();
whereStack.push(filter);
}
@@ -377,6 +377,20 @@ public class QCustomerTest {
assertThat(q.getGeneratedSql()).contains(" from be_customer t0 left join be_contact t1 on t1.customer_id = t0.id where (t1.id is null or (t1.first_name like ? and t1.first_name like ? escape'|')) order by t0.id");
}
@Test
void filterManyOr() {
var q = new QCustomer()
.contacts.filterMany(c ->
c.or()
.firstName.startsWith("R")
.lastName.startsWith("R")
.endOr())
.query();
q.findList();
assertThat(q.getGeneratedSql()).contains(" from be_customer t0 left join be_contact t1 on t1.customer_id = t0.id where (t1.id is null or ((t1.first_name like ? escape'|' or t1.last_name like ? escape'|'))) order by t0.id");
}
@Test
public void testIdIn() {