package io.ebeaninternal.server.expression;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
import io.ebean.FetchPath;
import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.Junction;
import io.ebean.OrderBy;
import io.ebean.PagedList;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.Version;
import io.ebean.event.BeanQueryRequest;
import io.ebean.search.Match;
import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import io.ebeaninternal.api.HashQueryPlanBuilder;
import io.ebeaninternal.api.ManyWhereJoins;
import io.ebeaninternal.api.SpiExpression;
import io.ebeaninternal.api.SpiExpressionList;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiExpressionValidation;
import io.ebeaninternal.api.SpiJunction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Default implementation of ExpressionList.
*/
public class DefaultExpressionList<T> implements SpiExpressionList<T> {
private static final String AND = " and ";
protected List<SpiExpression> list;
protected final Query<T> query;
private final ExpressionList<T> parentExprList;
protected ExpressionFactory expr;
String allDocNestedPath;
/**
* Set to true for the "Text" root expression list.
*/
private final boolean textRoot;
/**
* 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);
}
DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList) {
this(query, expr, parentExprList, new ArrayList<>());
}
DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list) {
this(query, expr, parentExprList, list, false);
}
private DefaultExpressionList(Query<T> query, ExpressionFactory expr, ExpressionList<T> parentExprList, List<SpiExpression> list, boolean textRoot) {
this.textRoot = textRoot;
this.list = list;
this.query = query;
this.expr = expr;
this.parentExprList = parentExprList;
}
private DefaultExpressionList() {
this(null, null, null, new ArrayList<>());
}
/**
* Wrap the expression list as a Junction or top level DefaultExpressionList.
*
* @param list The list of expressions grouped by nested path
* @param nestedPath The doc store nested path
* @param type The junction type (or null for top level expression list).
* @return A single SpiExpression that has the nestedPath set
*/
SpiExpression wrap(List<SpiExpression> list, String nestedPath, Junction.Type type) {
DefaultExpressionList<T> wrapper = new DefaultExpressionList<>(query, expr, null, list, false);
wrapper.setAllDocNested(nestedPath);
if (type != null) {
return new JunctionExpression<>(type, wrapper);
} else {
return wrapper;
}
}
void simplifyEntries() {
for (SpiExpression element : list) {
element.simplify();
}
}
@Override
public Junction<T> toJunction() {
return new JunctionExpression<>(Junction.Type.FILTER, this);
}
@Override
public void simplify() {
simplifyEntries();
}
/**
* Write being aware if it is the Top level "text" expressions.
* <p>
* If this is the Top level "text" expressions then it detects if explicit or implicit Bool Should, Must etc is required
* to wrap the expressions.
* </p>
* <p>
* If implicit Bool is required SHOULD is used.
* </p>
*/
@Override
public void writeDocQuery(DocQueryContext context) throws IOException {
if (!textRoot) {
writeDocQuery(context, null);
} else {
// this is a Top level "text" expressions so we may need to wrap in Bool SHOULD etc.
if (list.isEmpty()) {
throw new IllegalStateException("empty expression list?");
}
if (allDocNestedPath != null) {
context.startNested(allDocNestedPath);
}
int size = list.size();
SpiExpression first = list.get(0);
boolean explicitBool = first instanceof SpiJunction<?>;
boolean implicitBool = !explicitBool && size > 1;
if (implicitBool || explicitBool) {
context.startBoolGroup();
}
if (implicitBool) {
context.startBoolGroupList(Junction.Type.SHOULD);
}
for (SpiExpression expr : list) {
if (explicitBool) {
try {
((SpiJunction<?>) expr).writeDocQueryJunction(context);
} catch (ClassCastException e) {
throw new IllegalStateException("The top level text() expressions should be all be 'Must', 'Should' or 'Must Not' or none of them should be.", e);
}
} else {
expr.writeDocQuery(context);
}
}
if (implicitBool) {
context.endBoolGroupList();
}
if (implicitBool || explicitBool) {
context.endBoolGroup();
}
if (allDocNestedPath != null) {
context.endNested();
}
}
}
@Override
public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException {
if (allDocNestedPath != null) {
context.startNested(allDocNestedPath);
}
int size = list.size();
if (size == 1 && idEquals == null) {
// only 1 expression - skip bool
list.get(0).writeDocQuery(context);
} else if (size == 0 && idEquals != null) {
// only idEquals - skip bool
idEquals.writeDocQuery(context);
} else {
// bool must wrap all the children
context.startBoolMust();
if (idEquals != null) {
idEquals.writeDocQuery(context);
}
for (SpiExpression aList : list) {
aList.writeDocQuery(context);
}
context.endBool();
}
if (allDocNestedPath != null) {
context.endNested();
}
}
@Override
public SpiExpressionList<?> trimPath(int prefixTrim) {
throw new IllegalStateException("Only allowed on FilterExpressionList");
}
public List<SpiExpression> internalList() {
return list;
}
/**
* Return a copy of the expression list.
* <p>
* Each of the expressions are expected to be immutable and safe to reference.
* </p>
*/
public DefaultExpressionList<T> copy(Query<T> query) {
DefaultExpressionList<T> copy = new DefaultExpressionList<>(query, expr, null);
copy.list.addAll(list);
return copy;
}
@Override
public DefaultExpressionList<T> copyForPlanKey() {
DefaultExpressionList<T> copy = new DefaultExpressionList<>();
for (int i = 0; i < list.size(); i++) {
copy.list.add(list.get(i).copyForPlanKey());
}
return copy;
}
@Override
public Object getIdEqualTo(String idName) {
// always return null for this expression
return null;
}
/**
* Return true if one of the expressions is related to a Many property.
*/
@Override
public void containsMany(BeanDescriptor<?> desc, ManyWhereJoins whereManyJoins) {
for (SpiExpression aList : list) {
aList.containsMany(desc, whereManyJoins);
}
}
@Override
public void validate(SpiExpressionValidation validation) {
for (SpiExpression aList : list) {
aList.validate(validation);
}
}
@Override
public Query<T> query() {
return query;
}
@Override
public Query<T> asOf(Timestamp asOf) {
return query.asOf(asOf);
}
@Override
public Query<T> asDraft() {
return query.asDraft();
}
@Override
public Query<T> setIncludeSoftDeletes() {
return query.setIncludeSoftDeletes();
}
@Override
public List<Version<T>> findVersions() {
return query.findVersions();
}
@Override
public List<Version<T>> findVersionsBetween(Timestamp start, Timestamp end) {
return query.findVersionsBetween(start, end);
}
@Override
public ExpressionList<T> where() {
return query.where();
}
@Override
public OrderBy<T> order() {
return query.order();
}
@Override
public OrderBy<T> orderBy() {
return query.order();
}
@Override
public Query<T> order(String orderByClause) {
return query.order(orderByClause);
}
@Override
public Query<T> orderBy(String orderBy) {
return query.order(orderBy);
}
@Override
public Query<T> setOrderBy(String orderBy) {
return query.order(orderBy);
}
@Override
public Query<T> apply(FetchPath fetchPath) {
return query.apply(fetchPath);
}
@Override
public int delete() {
return query.delete();
}
@Override
public int update() {
return query.update();
}
@Override
public FutureIds<T> findFutureIds() {
return query.findFutureIds();
}
@Override
public FutureRowCount<T> findFutureCount() {
return query.findFutureCount();
}
@Override
public FutureList<T> findFutureList() {
return query.findFutureList();
}
@Override
public PagedList<T> findPagedList() {
return query.findPagedList();
}
@Override
public int findCount() {
return query.findCount();
}
@Override
public <A> List<A> findIds() {
return query.findIds();
}
@Override
public QueryIterator<T> findIterate() {
return query.findIterate();
}
@Override
public void findEach(Consumer<T> consumer) {
query.findEach(consumer);
}
@Override
public void findEachWhile(Predicate<T> consumer) {
query.findEachWhile(consumer);
}
@Override
public List<T> findList() {
return query.findList();
}
@Override
public Set<T> findSet() {
return query.findSet();
}
@Override
public <K> Map<K, T> findMap() {
return query.findMap();
}
@Override
public <A> List<A> findSingleAttributeList() {
return query.findSingleAttributeList();
}
@Override
public T findOne() {
return query.findOne();
}
@Override
public Optional<T> findOneOrEmpty() {
return query.findOneOrEmpty();
}
@Override
public T findUnique() {
return query.findUnique();
}
@Override
public ExpressionList<T> filterMany(String prop) {
return query.filterMany(prop);
}
@Override
public Query<T> forUpdate() {
return query.forUpdate();
}
@Override
public Query<T> forUpdateNoWait() {
return query.forUpdateNoWait();
}
@Override
public Query<T> forUpdateSkipLocked() {
return query.forUpdateSkipLocked();
}
@Override
public Query<T> select(String fetchProperties) {
return query.select(fetchProperties);
}
@Override
public Query<T> setDistinct(boolean distinct) {
return query.setDistinct(distinct);
}
@Override
public Query<T> setDocIndexName(String indexName) {
return query.setDocIndexName(indexName);
}
@Override
public Query<T> setFirstRow(int firstRow) {
return query.setFirstRow(firstRow);
}
@Override
public Query<T> setMaxRows(int maxRows) {
return query.setMaxRows(maxRows);
}
@Override
public Query<T> setMapKey(String mapKey) {
return query.setMapKey(mapKey);
}
@Override
public Query<T> setUseCache(boolean useCache) {
return query.setUseCache(useCache);
}
@Override
public Query<T> setUseQueryCache(boolean useCache) {
return query.setUseQueryCache(useCache);
}
@Override
public Query<T> setUseDocStore(boolean useDocsStore) {
return query.setUseDocStore(useDocsStore);
}
@Override
public Query<T> setDisableLazyLoading(boolean disableLazyLoading) {
return query.setDisableLazyLoading(disableLazyLoading);
}
@Override
public Query<T> setDisableReadAuditing() {
return query.setDisableReadAuditing();
}
@Override
public ExpressionList<T> having() {
return query.having();
}
@Override
public ExpressionList<T> add(Expression expr) {
list.add((SpiExpression) expr);
return this;
}
@Override
public ExpressionList<T> addAll(ExpressionList<T> exprList) {
SpiExpressionList<T> spiList = (SpiExpressionList<T>) exprList;
list.addAll(spiList.getUnderlyingList());
return this;
}
@Override
public List<SpiExpression> getUnderlyingList() {
return list;
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public void addSql(SpiExpressionRequest request) {
for (int i = 0, size = list.size(); i < size; i++) {
SpiExpression expression = list.get(i);
if (i > 0) {
request.append(AND);
}
expression.addSql(request);
}
}
@Override
public void addBindValues(SpiExpressionRequest request) {
for (SpiExpression aList : list) {
aList.addBindValues(request);
}
}
@Override
public void prepareExpression(BeanQueryRequest<?> request) {
for (SpiExpression aList : list) {
aList.prepareExpression(request);
}
}
/**
* Calculate a hash based on the expressions but excluding the actual bind
* values.
*/
@Override
public void queryPlanHash(HashQueryPlanBuilder builder) {
builder.add(DefaultExpressionList.class);
for (SpiExpression aList : list) {
aList.queryPlanHash(builder);
}
}
/**
* Calculate a hash based on the expressions.
*/
@Override
public int queryBindHash() {
int hash = DefaultExpressionList.class.getName().hashCode();
for (SpiExpression aList : list) {
hash = hash * 92821 + aList.queryBindHash();
}
return hash;
}
@Override
public boolean isSameByPlan(SpiExpression other) {
if (!(other instanceof DefaultExpressionList)) {
return false;
}
DefaultExpressionList<?> that = (DefaultExpressionList<?>) other;
if (list.size() != that.list.size()) {
return false;
}
for (int i = 0, size = list.size(); i < size; i++) {
if (!list.get(i).isSameByPlan(that.list.get(i))) {
return false;
}
}
return true;
}
@Override
public boolean isSameByBind(SpiExpression other) {
DefaultExpressionList<?> that = (DefaultExpressionList<?>) other;
if (list.size() != that.list.size()) {
return false;
}
for (int i = 0, size = list.size(); i < size; i++) {
if (!list.get(i).isSameByBind(that.list.get(i))) {
return false;
}
}
return true;
}
/**
* Path exists - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonExists(String propertyName, String path) {
add(expr.jsonExists(propertyName, path));
return this;
}
/**
* Path does not exist - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotExists(String propertyName, String path) {
add(expr.jsonNotExists(propertyName, path));
return this;
}
/**
* Equal to expression for the value at the given path in the JSON document.
*/
@Override
public ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value) {
add(expr.jsonEqualTo(propertyName, path, value));
return this;
}
/**
* Not Equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object val) {
add(expr.jsonNotEqualTo(propertyName, path, val));
return this;
}
/**
* Greater than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val) {
add(expr.jsonGreaterThan(propertyName, path, val));
return this;
}
/**
* Greater than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val) {
add(expr.jsonGreaterOrEqual(propertyName, path, val));
return this;
}
/**
* Less than - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessThan(String propertyName, String path, Object val) {
add(expr.jsonLessThan(propertyName, path, val));
return this;
}
/**
* Less than or equal to - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val) {
add(expr.jsonLessOrEqualTo(propertyName, path, val));
return this;
}
/**
* Between - for the given path in a JSON document.
*/
@Override
public ExpressionList<T> jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue) {
add(expr.jsonBetween(propertyName, path, lowerValue, upperValue));
return this;
}
@Override
public ExpressionList<T> eq(String propertyName, Object value) {
add(expr.eq(propertyName, value));
return this;
}
@Override
public ExpressionList<T> ieq(String propertyName, String value) {
add(expr.ieq(propertyName, value));
return this;
}
@Override
public ExpressionList<T> ne(String propertyName, Object value) {
add(expr.ne(propertyName, value));
return this;
}
@Override
public ExpressionList<T> allEq(Map<String, Object> propertyMap) {
add(expr.allEq(propertyMap));
return this;
}
@Override
public ExpressionList<T> and(Expression expOne, Expression expTwo) {
add(expr.and(expOne, expTwo));
return this;
}
@Override
public ExpressionList<T> between(String propertyName, Object value1, Object value2) {
add(expr.between(propertyName, value1, value2));
return this;
}
@Override
public ExpressionList<T> betweenProperties(String lowProperty, String highProperty, Object value) {
add(expr.betweenProperties(lowProperty, highProperty, value));
return this;
}
@Override
public ExpressionList<T> contains(String propertyName, String value) {
add(expr.contains(propertyName, value));
return this;
}
@Override
public ExpressionList<T> endsWith(String propertyName, String value) {
add(expr.endsWith(propertyName, value));
return this;
}
@Override
public ExpressionList<T> ge(String propertyName, Object value) {
add(expr.ge(propertyName, value));
return this;
}
@Override
public ExpressionList<T> gt(String propertyName, Object value) {
add(expr.gt(propertyName, value));
return this;
}
@Override
public ExpressionList<T> icontains(String propertyName, String value) {
add(expr.icontains(propertyName, value));
return this;
}
@Override
public ExpressionList<T> idIn(Object... idValues) {
add(expr.idIn(idValues));
return this;
}
@Override
public ExpressionList<T> idIn(List<?> idList) {
add(expr.idIn(idList));
return this;
}
@Override
public ExpressionList<T> idEq(Object value) {
if (query != null && parentExprList == null) {
query.setId(value);
} else {
add(expr.idEq(value));
}
return this;
}
@Override
public ExpressionList<T> iendsWith(String propertyName, String value) {
add(expr.iendsWith(propertyName, value));
return this;
}
@Override
public ExpressionList<T> ilike(String propertyName, String value) {
add(expr.ilike(propertyName, value));
return this;
}
@Override
public ExpressionList<T> in(String propertyName, Query<?> subQuery) {
add(expr.in(propertyName, subQuery));
return this;
}
@Override
public ExpressionList<T> in(String propertyName, Collection<?> values) {
add(expr.in(propertyName, values));
return this;
}
@Override
public ExpressionList<T> in(String propertyName, Object... values) {
add(expr.in(propertyName, values));
return this;
}
@Override
public ExpressionList<T> notIn(String propertyName, Object... values) {
add(expr.notIn(propertyName, values));
return this;
}
@Override
public ExpressionList<T> notIn(String propertyName, Collection<?> values) {
add(expr.notIn(propertyName, values));
return this;
}
@Override
public ExpressionList<T> notIn(String propertyName, Query<?> subQuery) {
add(expr.notIn(propertyName, subQuery));
return this;
}
@Override
public ExpressionList<T> isEmpty(String propertyName) {
add(expr.isEmpty(propertyName));
return this;
}
@Override
public ExpressionList<T> isNotEmpty(String propertyName) {
add(expr.isNotEmpty(propertyName));
return this;
}
@Override
public ExpressionList<T> exists(Query<?> subQuery) {
add(expr.exists(subQuery));
return this;
}
@Override
public ExpressionList<T> notExists(Query<?> subQuery) {
add(expr.notExists(subQuery));
return this;
}
@Override
public ExpressionList<T> isNotNull(String propertyName) {
add(expr.isNotNull(propertyName));
return this;
}
@Override
public ExpressionList<T> isNull(String propertyName) {
add(expr.isNull(propertyName));
return this;
}
@Override
public ExpressionList<T> istartsWith(String propertyName, String value) {
add(expr.istartsWith(propertyName, value));
return this;
}
@Override
public ExpressionList<T> le(String propertyName, Object value) {
add(expr.le(propertyName, value));
return this;
}
@Override
public ExpressionList<T> exampleLike(Object example) {
add(expr.exampleLike(example));
return this;
}
@Override
public ExpressionList<T> iexampleLike(Object example) {
add(expr.iexampleLike(example));
return this;
}
@Override
public ExpressionList<T> like(String propertyName, String value) {
add(expr.like(propertyName, value));
return this;
}
@Override
public ExpressionList<T> lt(String propertyName, Object value) {
add(expr.lt(propertyName, value));
return this;
}
@Override
public ExpressionList<T> not(Expression exp) {
add(expr.not(exp));
return this;
}
@Override
public ExpressionList<T> or(Expression expOne, Expression expTwo) {
add(expr.or(expOne, expTwo));
return this;
}
@Override
public ExpressionList<T> arrayContains(String propertyName, Object... elementValue) {
add(expr.arrayContains(propertyName, elementValue));
return this;
}
@Override
public ExpressionList<T> arrayNotContains(String propertyName, Object... values) {
add(expr.arrayNotContains(propertyName, values));
return this;
}
@Override
public ExpressionList<T> arrayIsEmpty(String propertyName) {
add(expr.arrayIsEmpty(propertyName));
return this;
}
@Override
public ExpressionList<T> arrayIsNotEmpty(String propertyName) {
add(expr.arrayIsNotEmpty(propertyName));
return this;
}
@Override
public ExpressionList<T> raw(String raw, Object value) {
add(expr.raw(raw, value));
return this;
}
@Override
public ExpressionList<T> raw(String raw, Object... values) {
add(expr.raw(raw, values));
return this;
}
@Override
public ExpressionList<T> raw(String raw) {
add(expr.raw(raw));
return this;
}
@Override
public ExpressionList<T> startsWith(String propertyName, String value) {
add(expr.startsWith(propertyName, value));
return this;
}
@Override
public ExpressionList<T> match(String propertyName, String search) {
return match(propertyName, search, null);
}
@Override
public ExpressionList<T> match(String propertyName, String search, Match options) {
add(expr.textMatch(propertyName, search, options));
setUseDocStore(true);
return this;
}
@Override
public ExpressionList<T> multiMatch(String query, String... fields) {
return multiMatch(query, MultiMatch.fields(fields));
}
@Override
public ExpressionList<T> multiMatch(String query, MultiMatch options) {
setUseDocStore(true);
add(expr.textMultiMatch(query, options));
return this;
}
@Override
public ExpressionList<T> textSimple(String search, TextSimple options) {
setUseDocStore(true);
add(expr.textSimple(search, options));
return this;
}
@Override
public ExpressionList<T> textQueryString(String search, TextQueryString options) {
setUseDocStore(true);
add(expr.textQueryString(search, options));
return this;
}
@Override
public ExpressionList<T> textCommonTerms(String search, TextCommonTerms options) {
setUseDocStore(true);
add(expr.textCommonTerms(search, options));
return this;
}
private Junction<T> junction(Junction.Type type) {
Junction<T> junction = expr.junction(type, query, this);
add(junction);
return junction;
}
@Override
public ExpressionList<T> endJunction() {
return parentExprList == null ? this : parentExprList;
}
@Override
public ExpressionList<T> endAnd() {
return endJunction();
}
@Override
public ExpressionList<T> endOr() {
return endJunction();
}
@Override
public ExpressionList<T> endNot() {
return endJunction();
}
@Override
public Junction<T> and() {
return conjunction();
}
@Override
public Junction<T> or() {
return disjunction();
}
@Override
public Junction<T> not() {
return junction(Junction.Type.NOT);
}
@Override
public Junction<T> conjunction() {
return junction(Junction.Type.AND);
}
@Override
public Junction<T> disjunction() {
return junction(Junction.Type.OR);
}
@Override
public Junction<T> must() {
setUseDocStore(true);
return junction(Junction.Type.MUST);
}
@Override
public Junction<T> should() {
setUseDocStore(true);
return junction(Junction.Type.SHOULD);
}
@Override
public Junction<T> mustNot() {
setUseDocStore(true);
return junction(Junction.Type.MUST_NOT);
}
@Override
public String nestedPath(BeanDescriptor<?> desc) {
// effectively handled by JunctionExpression
return null;
}
/**
* Set the nested path that all contained expressions share.
*/
public void setAllDocNested(String allDocNestedPath) {
this.allDocNestedPath = allDocNestedPath;
}
/**
* Replace the underlying expression list with one organised by nested path.
*/
public void setUnderlying(List<SpiExpression> groupedByNesting) {
this.list = groupedByNesting;
}
/**
* Prepare expressions for document store nested path handling.
*/
public void prepareDocNested(BeanDescriptor<T> beanDescriptor) {
PrepareDocNested.prepare(this, beanDescriptor);
}
public Object idEqualTo(String idName) {
if (list.size() == 1) {
return list.get(0).getIdEqualTo(idName);
}
return null;
}
}